diff options
356 files changed, 108300 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..01a129a --- /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/ngtcp2 +WORKDIR ngtcp2 +COPY .clusterfuzzlite/build.sh $SRC/ diff --git a/.clusterfuzzlite/build.sh b/.clusterfuzzlite/build.sh new file mode 100755 index 0000000..0ac681c --- /dev/null +++ b/.clusterfuzzlite/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash -eu + +autoreconf -i +./configure --enable-lib-only +make -j$(nproc) + +$CXX $CXXFLAGS -std=c++17 -Ilib/includes -Ilib -I. -DHAVE_CONFIG_H \ + fuzz/decode_frame.cc -o $OUT/decode_frame \ + $LIB_FUZZING_ENGINE lib/.libs/libngtcp2.a + +$CXX $CXXFLAGS -std=c++17 -Ilib/includes -Ilib -I. -DHAVE_CONFIG_H \ + fuzz/ksl.cc -o $OUT/ksl \ + $LIB_FUZZING_ENGINE lib/.libs/libngtcp2.a + +zip -j $OUT/decode_frame_seed_corpus.zip fuzz/corpus/decode_frame/* +zip -j $OUT/ksl_seed_corpus.zip fuzz/corpus/ksl/* 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..0c2abfc --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,299 @@ +name: build + +on: [push, pull_request] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-22.04, macos-11] + compiler: [gcc, clang] + buildtool: [autotools, distcheck, cmake] + openssl: [openssl1, openssl3] + sockaddr: [native-sockaddr, generic-sockaddr] + exclude: + - os: macos-11 + buildtool: distcheck + - compiler: gcc + buildtool: distcheck + - openssl: openssl3 + buildtool: distcheck + - compiler: gcc + sockaddr: generic-sockaddr + - openssl: openssl3 + sockaddr: generic-sockaddr + - buildtool: distcheck + sockaddr: generic-sockaddr + - buildtool: cmake + sockaddr: generic-sockaddr + - os: macos-11 + compiler: gcc + - os: macos-11 + openssl: openssl3 + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + - name: Startup + run: | + echo 'NGTCP2_SOURCE_DIR='"$PWD" >> $GITHUB_ENV + - name: Linux setup + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install \ + g++-12 \ + clang-14 \ + autoconf \ + automake \ + autotools-dev \ + libtool \ + pkg-config \ + libcunit1-dev \ + libssl-dev \ + libev-dev \ + libgnutls28-dev \ + cmake \ + cmake-data \ + python3-pytest + + echo 'NPROC='"$(nproc)" >> $GITHUB_ENV + - name: MacOS setup + if: runner.os == 'macOS' + run: | + brew install libev gnutls cunit autoconf automake pkg-config libtool + + echo 'NPROC='"$(sysctl -n hw.ncpu)" >> $GITHUB_ENV + - 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 + if: runner.os == 'Linux' && matrix.compiler == 'gcc' + run: | + echo 'CC=gcc-12' >> $GITHUB_ENV + echo 'CXX=g++-12' >> $GITHUB_ENV + - name: Build OpenSSL v1.1.1 + if: matrix.openssl == 'openssl1' + run: | + ./ci/build_openssl1.sh + - name: Build OpenSSL v3.0.x + if: matrix.openssl == 'openssl3' + run: | + ./ci/build_openssl3.sh + - name: Build BoringSSL + run: | + ./ci/build_boringssl.sh + - name: Build Picotls + run: | + ./ci/build_picotls.sh + - name: Build wolfSSL + run: | + ./ci/build_wolfssl.sh + - name: Build nghttp3 + run: | + ./ci/build_nghttp3.sh + - name: Setup environment variables + run: | + PKG_CONFIG_PATH="$PWD/openssl/build/lib/pkgconfig:$PWD/openssl/build/lib64/pkgconfig:$PWD/wolfssl/build/lib/pkgconfig:$PWD/nghttp3/build/lib/pkgconfig" + LDFLAGS="$EXTRA_LDFLAGS -Wl,-rpath,$PWD/openssl/build/lib -Wl,-rpath,$PWD/openssl/build/lib64" + BORINGSSL_CFLAGS="-I$PWD/boringssl/include/" + BORINGSSL_LIBS="-L$PWD/boringssl/build/ssl -lssl -L$PWD/boringssl/build/crypto -lcrypto" + PICOTLS_CFLAGS="-I$PWD/picotls/include/" + PICOTLS_LIBS="-L$PWD/picotls/build -lpicotls-openssl -lpicotls-core" + + echo 'PKG_CONFIG_PATH='"$PKG_CONFIG_PATH" >> $GITHUB_ENV + echo 'LDFLAGS='"$LDFLAGS" >> $GITHUB_ENV + echo 'BORINGSSL_CFLAGS='"$BORINGSSL_CFLAGS" >> $GITHUB_ENV + echo 'BORINGSSL_LIBS='"$BORINGSSL_LIBS" >> $GITHUB_ENV + echo 'PICOTLS_CFLAGS='"$PICOTLS_CFLAGS" >> $GITHUB_ENV + echo 'PICOTLS_LIBS='"$PICOTLS_LIBS" >> $GITHUB_ENV + - name: Enable ASAN + if: runner.os == 'Linux' + run: | + asanflags="-fsanitize=address,undefined -fno-sanitize-recover=undefined" + + LDFLAGS="$LDFLAGS $asanflags" + CFLAGS="$CFLAGS $asanflags -g3" + CXXFLAGS="$CXXFLAGS $asanflags -g3" + + echo 'LDFLAGS='"$LDFLAGS" >> $GITHUB_ENV + echo 'CFLAGS='"$CFLAGS" >> $GITHUB_ENV + echo 'CXXFLAGS='"$CXXFLAGS" >> $GITHUB_ENV + - name: BoringSSL pthread requirement + if: runner.os == 'Linux' + run: | + BORINGSSL_LIBS="$BORINGSSL_LIBS -pthread" + + echo 'BORINGSSL_LIBS='"$BORINGSSL_LIBS" >> $GITHUB_ENV + - name: Enable generic sockaddr + if: matrix.sockaddr == 'generic-sockaddr' + run: | + CFLAGS="$CFLAGS -DNGTCP2_USE_GENERIC_SOCKADDR" + EXTRA_AUTOTOOLS_OPTS="$EXTRA_AUTOTOOLS_OPTS --enable-lib-only" + + echo 'CFLAGS='"$CFLAGS" >> $GITHUB_ENV + echo 'EXTRA_AUTOTOOLS_OPTS='"$EXTRA_AUTOTOOLS_OPTS" >> $GITHUB_ENV + - name: Configure autotools + if: matrix.buildtool == 'autotools' + run: | + autoreconf -i && \ + ./configure --enable-werror \ + --with-openssl --with-gnutls --with-boringssl --with-wolfssl \ + --with-picotls \ + $EXTRA_AUTOTOOLS_OPTS + - name: Configure autotools for 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 ngtcp2-$VERSION.tar.gz + cd ngtcp2-$VERSION + mkdir build + cd build + + echo 'NGTCP2_BUILD_DIR='"$PWD" >> $GITHUB_ENV + + cmake $CMAKE_OPTS \ + -DENABLE_GNUTLS=ON \ + -DENABLE_BORINGSSL=ON \ + -DBORINGSSL_LIBRARIES="$BORINGSSL_LIBS" \ + -DBORINGSSL_INCLUDE_DIR="$NGTCP2_SOURCE_DIR/boringssl/include/" \ + -DENABLE_PICOTLS=ON \ + -DPICOTLS_LIBRARIES="$PICOTLS_LIBS" \ + -DPICOTLS_INCLUDE_DIR="$NGTCP2_SOURCE_DIR/picotls/include/" \ + -DENABLE_WOLFSSL=ON .. + - name: Build ngtcp2 + if: matrix.buildtool != 'distcheck' + run: | + [ -n "$NGTCP2_BUILD_DIR" ] && cd "$NGTCP2_BUILD_DIR" + make -j"$NPROC" + make -j"$NPROC" check + - name: Build ngtcp2 with distcheck + if: matrix.buildtool == 'distcheck' + run: | + make -j"$NPROC" distcheck \ + DISTCHECK_CONFIGURE_FLAGS="--enable-werror --with-openssl --with-gnutls --with-boringssl $EXTRA_AUTOTOOLS_OPTS" + - name: examples/tests + if: matrix.buildtool == 'autotools' && matrix.sockaddr == 'native-sockaddr' && runner.os == 'Linux' + run: | + cd examples/tests + # Do not run resumption and earlydata between gtlsserver and + # wsslclient until ubuntu gets GnuTLS v3.7.5. + # + # There is an issue around the ticket age validation between + # gtlsserver and bsslclient that causes early data test + # failure; see https://gitlab.com/gnutls/gnutls/-/issues/1403 + pytest-3 -v -k 'not (gnutls-wolfssl and (resume or earlydata)) and not (gnutls-boringssl and earlydata)' + - name: Integration test + if: matrix.buildtool != 'distcheck' && matrix.sockaddr == 'native-sockaddr' + run: | + [ -n "$NGTCP2_BUILD_DIR" ] && cd "$NGTCP2_BUILD_DIR" + "$NGTCP2_SOURCE_DIR"/ci/gen-certificate.sh + + CLIENTS="client gtlsclient bsslclient wsslclient ptlsclient" + SERVERS="server gtlsserver bsslserver wsslserver ptlsserver" + + for client in $CLIENTS; do + for server in $SERVERS; do + echo "# $client - $server" + ./examples/$server localhost 4433 cert/server.key cert/server.crt > sv.log 2>&1 & + SVPID="$!" + ./examples/$client localhost 4433 https://localhost/ --exit-on-first-stream-close + kill -INT "$SVPID" + sleep 5 + if wait "$SVPID"; then + cat sv.log + else + cat sv.log + exit 1 + fi + done + done + + build-cross: + strategy: + matrix: + host: [x86_64-w64-mingw32, i686-w64-mingw32] + include: + - host: x86_64-w64-mingw32 + oscc: mingw64 + - host: i686-w64-mingw32 + oscc: mingw + + runs-on: ubuntu-22.04 + + env: + HOST: ${{ matrix.host }} + OSCC: ${{ matrix.oscc }} + + 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 OpenSSL v1.1.1 + run: | + ./ci/build_openssl1_cross.sh + - 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: Setup environment variables + run: | + PKG_CONFIG_PATH="$PWD/openssl/build/lib/pkgconfig:$PWD/CUnit-2.1-3/build/lib/pkgconfig" + + echo 'PKG_CONFIG_PATH='"$PKG_CONFIG_PATH" >> $GITHUB_ENV + - name: Configure autotools + run: | + autoreconf -i && \ + ./configure --enable-werror --with-openssl --with-cunit --host="$HOST" + - name: Build ngtcp2 + 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 .. + - name: Build ngtcp2 + run: | + cmake --build build diff --git a/.github/workflows/cflite_batch.yaml b/.github/workflows/cflite_batch.yaml new file mode 100644 index 0000000..3bda75d --- /dev/null +++ b/.github/workflows/cflite_batch.yaml @@ -0,0 +1,30 @@ +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: + language: c++ + 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/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..bb3510a --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,47 @@ +name: "Code scanning - action" + +on: + push: + pull_request: + schedule: + - cron: '0 6 * * *' + +jobs: + CodeQL-Build: + + # CodeQL runs on ubuntu-latest and windows-latest + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + # Override language selection by uncommenting this and choosing your languages + # with: + # languages: go, javascript, csharp, python, cpp, java + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # â„¹ï¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœï¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/interop.yml b/.github/workflows/interop.yml new file mode 100644 index 0000000..1a41bf6 --- /dev/null +++ b/.github/workflows/interop.yml @@ -0,0 +1,28 @@ +name: interop + +on: + push: + branches: + - main + +jobs: + build: + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and publish interop docker image + uses: docker/build-push-action@v3 + with: + context: interop + push: true + tags: ghcr.io/ngtcp2/ngtcp2-interop:latest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..146dcfc --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# emacs backup file +*~ + +# autotools +*.la +*.lo +*.m4 +*.o +*.pyc +.dirstamp +.deps/ +.libs/ +INSTALL +Makefile +Makefile.in +autom4te.cache/ +cert/ +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 + +# idea settings +.idea @@ -0,0 +1,44 @@ +Alexis La Goutte +Amir Livneh +Anna Henningsen +Bryan Call +Daan De Meyer +Daiki Ueno +Daniel Bevenius +Daniel Stenberg +Dave Reisner +Don +Frédéric Lécaille +Félix Dagenais +James M Snell +Javier Blazquez +Jay Satiro +Jean-Philippe Boivin +Jiawen Geng +Junqi Wang +Ken-ichi ICHINO +Liang Ma +Martin Thomson +NKTelnet +Natris +Patrick Griffis +Peter Wu +Samuel Henrique +Stefan Eissing +Stefan Eissing +Tatsuhiro Tsujikawa +Tim Gates +Tomas Mraz +Toni Uhlig +Valère Plantevin +Victor Loh +Viktor Szakats +Zizhong Zhang +flx413 +hondaxiao +junqiw +msoxzw +nickfajones +rhoxn +scw00 +shibin k v diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..287e212 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,431 @@ +# ngtcp2 +# +# 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) + +# Allow setting VISIBILITY_PRESET on static library targets without warning. +cmake_policy(SET CMP0063 NEW) + +# XXX using 0.1.90 instead of 0.2.0-DEV +project(ngtcp2 VERSION 0.12.1) + +# See versioning rule: +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +set(LT_CURRENT 9) +set(LT_REVISION 1) +set(LT_AGE 0) + +set(CRYPTO_OPENSSL_LT_CURRENT 3) +set(CRYPTO_OPENSSL_LT_REVISION 0) +set(CRYPTO_OPENSSL_LT_AGE 1) + +set(CRYPTO_GNUTLS_LT_CURRENT 3) +set(CRYPTO_GNUTLS_LT_REVISION 0) +set(CRYPTO_GNUTLS_LT_AGE 1) + +set(CRYPTO_WOLFSSL_LT_CURRENT 0) +set(CRYPTO_WOLFSSL_LT_REVISION 1) +set(CRYPTO_WOLFSSL_LT_AGE 0) + +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}") +include(Version) + +math(EXPR LT_SOVERSION "${LT_CURRENT} - ${LT_AGE}") +set(LT_VERSION "${LT_SOVERSION}.${LT_AGE}.${LT_REVISION}") + +math(EXPR CRYPTO_OPENSSL_LT_SOVERSION + "${CRYPTO_OPENSSL_LT_CURRENT} - ${CRYPTO_OPENSSL_LT_AGE}") +set(CRYPTO_OPENSSL_LT_VERSION + "${CRYPTO_OPENSSL_LT_SOVERSION}.${CRYPTO_OPENSSL_LT_AGE}.${CRYPTO_OPENSSL_LT_REVISION}") + +math(EXPR CRYPTO_GNUTLS_LT_SOVERSION + "${CRYPTO_GNUTLS_LT_CURRENT} - ${CRYPTO_GNUTLS_LT_AGE}") +set(CRYPTO_GNUTLS_LT_VERSION + "${CRYPTO_GNUTLS_LT_SOVERSION}.${CRYPTO_GNUTLS_LT_AGE}.${CRYPTO_GNUTLS_LT_REVISION}") + +math(EXPR CRYPTO_WOLFSSL_LT_SOVERSION + "${CRYPTO_WOLFSSL_LT_CURRENT} - ${CRYPTO_WOLFSSL_LT_AGE}") +set(CRYPTO_WOLFSSL_LT_VERSION + "${CRYPTO_WOLFSSL_LT_SOVERSION}.${CRYPTO_WOLFSSL_LT_AGE}.${CRYPTO_WOLFSSL_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() + +include(CMakePushCheckState) +include(ExtractValidFlags) + +if(NOT CMAKE_C_COMPILER_ID MATCHES "MSVC") + if(ENABLE_ASAN) + 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_GNUTLS) + find_package(GnuTLS 3.7.2) +endif() +if(ENABLE_OPENSSL) + find_package(OpenSSL 1.1.1) +endif() +if(ENABLE_WOLFSSL) + find_package(wolfssl 5.5.0) +endif() +if(ENABLE_JEMALLOC) + find_package(Jemalloc) +endif() +find_package(Libev 4.11) +find_package(Libnghttp3 0.0.0) +find_package(CUnit 2.1) +enable_testing() +set(HAVE_CUNIT ${CUNIT_FOUND}) +if(HAVE_CUNIT) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}) +endif() + +# OpenSSL (required for libngtcp2_crypto_openssl, +# libngtcp2_crypto_picotls and examples) +include(CheckSymbolExists) +if(ENABLE_OPENSSL AND OPENSSL_FOUND) + set(VANILLA_OPENSSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + set(VANILLA_OPENSSL_LIBRARIES ${OPENSSL_LIBRARIES}) + set(HAVE_VANILLA_OPENSSL TRUE) + + # Until OpenSSL gains mainline support for QUIC, check for a patched version. + cmake_push_check_state() + set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") + set(CMAKE_REQUIRED_LIBRARIES "${OPENSSL_LIBRARIES}") + if(WIN32) + set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES}" "ws2_32" "bcrypt") + endif() + check_symbol_exists(SSL_is_quic "openssl/ssl.h" HAVE_SSL_IS_QUIC) + if(NOT HAVE_SSL_IS_QUIC) + message(WARNING "Disabling OpenSSL due to lack of QUIC support in ${OPENSSL_LIBRARIES}") + endif() + cmake_pop_check_state() +endif() +if(ENABLE_OPENSSL AND HAVE_SSL_IS_QUIC) + set(OPENSSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + set(HAVE_OPENSSL TRUE) + set(HAVE_CRYPTO TRUE) +else() + set(HAVE_OPENSSL FALSE) + set(OPENSSL_INCLUDE_DIRS "") + set(OPENSSL_LIBRARIES "") +endif() + +# BoringSSL (required for libngtcp2_crypto_boringssl) +if(ENABLE_BORINGSSL) + cmake_push_check_state() + set(CMAKE_REQUIRED_INCLUDES "${BORINGSSL_INCLUDE_DIR}") + set(CMAKE_REQUIRED_LIBRARIES "${BORINGSSL_LIBRARIES}") + check_symbol_exists(SSL_set_quic_early_data_context "openssl/ssl.h" HAVE_SSL_SET_QUIC_EARLY_DATA_CONTEXT) + if(NOT HAVE_SSL_SET_QUIC_EARLY_DATA_CONTEXT) + message(WARNING "Disabling BoringSSL due to lack of QUIC support in ${BORINGSSL_LIBRARIES}") + endif() + cmake_pop_check_state() +endif() +if(ENABLE_BORINGSSL AND HAVE_SSL_SET_QUIC_EARLY_DATA_CONTEXT) + set(BORINGSSL_INCLUDE_DIRS ${BORINGSSL_INCLUDE_DIR}) + set(HAVE_BORINGSSL TRUE) + set(HAVE_CRYPTO TRUE) +else() + set(HAVE_BORINGSSL FALSE) + set(BORINGSSL_INCLUDE_DIRS "") + set(BORINGSSL_LIBRARIES "") +endif() + +# jemalloc +set(HAVE_JEMALLOC ${JEMALLOC_FOUND}) +# libev (required for examples) +set(HAVE_LIBEV ${LIBEV_FOUND}) +# libnghttp3 (required for examples) +set(HAVE_LIBNGHTTP3 ${LIBNGHTTP3_FOUND}) + +# GnuTLS (required for libngtcp2_crypto_gnutls) +if(ENABLE_GNUTLS AND GNUTLS_FOUND) + set(GNUTLS_INCLUDE_DIRS ${GNUTLS_INCLUDE_DIR}) + set(HAVE_GNUTLS TRUE) + set(HAVE_CRYPTO TRUE) +else() + set(HAVE_GNUTLS FALSE) + set(GNUTLS_INCLUDE_DIRS "") + set(GNUTLS_LIBRARIES "") +endif() + +# Picotls (required for libngtcp2_crypto_picotls) +if(ENABLE_PICOTLS) + cmake_push_check_state() + set(CMAKE_REQUIRED_INCLUDES "${PICOTLS_INCLUDE_DIR}" "${VANILLA_OPENSSL_INCLUDE_DIRS}") + set(CMAKE_REQUIRED_LIBRARIES "${PICOTLS_LIBRARIES}" "${VANILLA_OPENSSL_LIBRARIES}") + check_symbol_exists(ptls_openssl_random_bytes "picotls.h;picotls/openssl.h" + HAVE_PTLS_OPENSSL_RANDOM_BYTES) + if(NOT HAVE_PTLS_OPENSSL_RANDOM_BYTES) + message(WARNING "Disabling Picotls because ptls_openssl_random_bytes not found in ${CMAKE_REQUIRED_LIBRARIES}") + endif() + cmake_pop_check_state() +endif() +if(ENABLE_PICOTLS AND HAVE_PTLS_OPENSSL_RANDOM_BYTES) + set(PICOTLS_INCLUDE_DIRS ${PICOTLS_INCLUDE_DIR}) + set(HAVE_PICOTLS TRUE) + set(HAVE_CRYPTO TRUE) +else() + set(HAVE_PICOTLS FALSE) + set(PICOTLS_INCLUDE_DIRS "") + set(PICOTLS_LIBRARIES "") +endif() + +# wolfSSL (required for libngtcp2_crypto_wolfssl) +if(ENABLE_WOLFSSL AND WOLFSSL_FOUND) + set(WOLFSSL_INCLUDE_DIRS ${WOLFSSL_INCLUDE_DIR}) + set(HAVE_WOLFSSL TRUE) + set(HAVE_CRYPTO TRUE) +else() + set(HAVE_WOLFSSL FALSE) + set(WOLFSSL_INCLUDE_DIRS "") + set(WOLFSSL_LIBRARIES "") +endif() + +# Checks for header files. +include(CheckIncludeFile) +check_include_file("arpa/inet.h" HAVE_ARPA_INET_H) +check_include_file("netinet/in.h" HAVE_NETINET_IN_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) +check_include_file("asm/types.h" HAVE_ASM_TYPES_H) +check_include_file("linux/netlink.h" HAVE_LINUX_NETLINK_H) +check_include_file("linux/rtnetlink.h" HAVE_LINUX_RTNETLINK_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. +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) + +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 + ) +endif() + +if(ENABLE_DEBUG) + set(DEBUGBUILD 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}") +# libngtcp2.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/libngtcp2.pc + lib/includes/ngtcp2/version.h +) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +if(APPLE) + add_definitions(-D__APPLE_USE_RFC_3542) +endif() + +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(crypto) +add_subdirectory(third-party) +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}') + Libs: + OpenSSL: ${HAVE_OPENSSL} (LIBS='${OPENSSL_LIBRARIES}') + Libev: ${HAVE_LIBEV} (LIBS='${LIBEV_LIBRARIES}') + Libnghttp3: ${HAVE_LIBNGHTTP3} (LIBS='${LIBNGHTTP3_LIBRARIES}') + GnuTLS: ${HAVE_GNUTLS} (LIBS='${GNUTLS_LIBRARIES}') + BoringSSL: ${HAVE_BORINGSSL} (LIBS='${BORINGSSL_LIBRARIES}') + Picotls: ${HAVE_PICOTLS} (LIBS='${PICOTLS_LIBRARIES}') + wolfSSL: ${HAVE_WOLFSSL} (LIBS='${WOLFSSL_LIBRARIES}') + Jemalloc: ${HAVE_JEMALLOC} (LIBS='${JEMALLOC_LIBRARIES}') +") diff --git a/CMakeOptions.txt b/CMakeOptions.txt new file mode 100644 index 0000000..090b3fc --- /dev/null +++ b/CMakeOptions.txt @@ -0,0 +1,17 @@ +# Features that can be enabled for cmake (see CMakeLists.txt) + +option(ENABLE_STATIC_LIB "Build as static libraries" ON) +option(ENABLE_SHARED_LIB "Build as shared libraries" ON) + +option(ENABLE_WERROR "Make compiler warnings fatal" OFF) +option(ENABLE_DEBUG "Turn on debug output" OFF) +option(ENABLE_ASAN "Enable AddressSanitizer (ASAN)" OFF) +option(ENABLE_JEMALLOC "Enable Jemalloc" OFF) + +option(ENABLE_GNUTLS "Enable GnuTLS crypto backend" OFF) +option(ENABLE_OPENSSL "Enable OpenSSL crypto backend (required for examples)" ON) +option(ENABLE_BORINGSSL "Enable BoringSSL crypto backend" OFF) +option(ENABLE_PICOTLS "Enable Picotls crypto backend" OFF) +option(ENABLE_WOLFSSL "Enable wolfSSL crypto backend" OFF) + +# vim: ft=cmake: @@ -0,0 +1,22 @@ +The MIT License + +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. 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..f097cb4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,60 @@ +# ngtcp2 + +# Copyright (c) 2016 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = lib tests doc + +if HAVE_CRYPTO +SUBDIRS += crypto +endif + +if ENABLE_EXAMPLES +SUBDIRS += third-party examples +endif + +dist_doc_DATA = README.rst + +ACLOCAL_AMFLAGS = -I m4 + +EXTRA_DIST = \ + cmakeconfig.h.in \ + CMakeLists.txt \ + CMakeOptions.txt \ + cmake/ExtractValidFlags.cmake \ + cmake/FindCUnit.cmake \ + cmake/FindLibev.cmake \ + cmake/FindLibnghttp3.cmake \ + cmake/Findwolfssl.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 thier +# 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/ngtcp2/*.h \ + crypto/*.{c,h} \ + crypto/openssl/*.c crypto/gnutls/*.c crypto/boringssl/*.c \ + crypto/wolfssl/*.c \ + crypto/includes/ngtcp2/*.h \ + examples/*.{cc,c,h} \ + fuzz/*.cc @@ -0,0 +1 @@ +See README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..936a8e0 --- /dev/null +++ b/README.rst @@ -0,0 +1,258 @@ +ngtcp2 +====== + +"Call it TCP/2. One More Time." + +ngtcp2 project is an effort to implement `RFC9000 +<https://datatracker.ietf.org/doc/html/rfc9000>`_ QUIC protocol. + +Documentation +------------- + +`Online documentation <https://nghttp2.org/ngtcp2/>`_ is available. + +Public test server +------------------ + +The following endpoints are available to try out ngtcp2 +implementation: + +- https://nghttp2.org:4433 +- https://nghttp2.org:4434 (requires address validation token) +- https://nghttp2.org (powered by `nghttpx + <https://nghttp2.org/documentation/nghttpx.1.html>`_) + + This endpoints sends Alt-Svc header field to clients if it is + accessed via HTTP/1.1 or HTTP/2 to tell them that HTTP/3 is + available at UDP 443. + +Requirements +------------ + +The libngtcp2 C library itself does not depend on any external +libraries. The example client, and server are written in C++20, and +should compile with the modern C++ compilers (e.g., clang >= 11.0, or +gcc >= 11.0). + +The following packages are required to configure the build system: + +- pkg-config >= 0.20 +- autoconf +- automake +- autotools-dev +- libtool + +libngtcp2 uses cunit for its unit test frame work: + +- cunit >= 2.1 + +To build sources under the examples directory, libev and nghttp3 are +required: + +- libev +- `nghttp3 <https://github.com/ngtcp2/nghttp3>`_ for HTTP/3 + +ngtcp2 crypto helper library, and client and server under examples +directory require at least one of the following TLS backends: + +- `OpenSSL with QUIC support + <https://github.com/quictls/openssl/tree/OpenSSL_1_1_1s+quic>`_ +- GnuTLS >= 3.7.2 +- BoringSSL (commit 31bad2514d21f6207f3925ba56754611c462a873) +- Picotls (commit 5e01b2b94dc77c500ed4fc0eaa77bd0cbe8e9274) +- wolfSSL >= 5.5.0 + +Build from git +-------------- + +.. code-block:: text + + $ git clone --depth 1 -b OpenSSL_1_1_1s+quic https://github.com/quictls/openssl + $ cd openssl + $ # For Linux + $ ./config enable-tls1_3 --prefix=$PWD/build + $ make -j$(nproc) + $ make install_sw + $ cd .. + $ git clone https://github.com/ngtcp2/nghttp3 + $ cd nghttp3 + $ autoreconf -i + $ ./configure --prefix=$PWD/build --enable-lib-only + $ make -j$(nproc) check + $ make install + $ cd .. + $ git clone https://github.com/ngtcp2/ngtcp2 + $ cd ngtcp2 + $ autoreconf -i + $ # For Mac users who have installed libev with MacPorts, append + $ # ',-L/opt/local/lib' to LDFLAGS, and also pass + $ # CPPFLAGS="-I/opt/local/include" to ./configure. + $ # For OpenSSL >= v3.0.0, replace "openssl/build/lib" with + $ # "openssl/build/lib64". + $ ./configure PKG_CONFIG_PATH=$PWD/../openssl/build/lib/pkgconfig:$PWD/../nghttp3/build/lib/pkgconfig LDFLAGS="-Wl,-rpath,$PWD/../openssl/build/lib" + $ make -j$(nproc) check + +Client/Server +------------- + +After successful build, the client and server executable should be +found under examples directory. They talk HTTP/3. + +Client +~~~~~~ + +.. code-block:: text + + $ examples/client [OPTIONS] <HOST> <PORT> [<URI>...] + +The notable options are: + +- ``-d``, ``--data=<PATH>``: Read data from <PATH> and send it to a + peer. + +Server +~~~~~~ + +.. code-block:: text + + $ examples/server [OPTIONS] <ADDR> <PORT> <PRIVATE_KEY_FILE> <CERTIFICATE_FILE> + +The notable options are: + +- ``-V``, ``--validate-addr``: Enforce stateless address validation. + +H09client/H09server +------------------- + +There are h09client and h09server which speak HTTP/0.9. They are +written just for `quic-interop-runner +<https://github.com/marten-seemann/quic-interop-runner>`_. They share +the basic functionalities with HTTP/3 client and server but have less +functions (e.g., h09client does not have a capability to send request +body, and h09server does not understand numeric request path, like +/1000). + +Resumption and 0-RTT +-------------------- + +In order to resume a session, a session ticket, and a transport +parameters must be fetched from server. First, run examples/client +with --session-file, and --tp-file options which specify a path to +session ticket, and transport parameter files respectively to save +them locally. + +Once these files are available, run examples/client with the same +arguments again. You will see that session is resumed in your log if +resumption succeeds. Resuming session makes server's first Handshake +packet pretty small because it does not send its certificates. + +To send 0-RTT data, after making sure that resumption works, use -d +option to specify a file which contains data to send. + +Token (Not something included in Retry packet) +---------------------------------------------- + +QUIC server might send a token to client after connection has been +established. Client can send this token in subsequent connection to +the server. Server verifies the token and if it succeeds, the address +validation completes and lifts some restrictions on server which might +speed up transfer. In order to save and/or load a token, +use --token-file option of examples/client. The given file is +overwritten if it already exists when storing a token. + +Crypto helper library +--------------------- + +In order to make TLS stack integration less painful, we provide a +crypto helper library which offers the basic crypto operations. + +The header file exists under crypto/includes/ngtcp2 directory. + +Each library file is built for a particular TLS backend. The +available crypto helper libraries are: + +- libngtcp2_crypto_openssl: Use OpenSSL as TLS backend +- libngtcp2_crypto_gnutls: Use GnuTLS as TLS backend +- libngtcp2_crypto_boringssl: Use BoringSSL as TLS backend +- libngtcp2_crypto_picotls: Use Picotls as TLS backend +- libngtcp2_crypto_wolfssl: Use wolfSSL as TLS backend + +Because BoringSSL and Picotls are an unversioned product, we only +tested their particular revision. See Requirements section above. + +We use Picotls with OpenSSL as crypto backend. It does not work with +OpenSSL >= 3.0.0. + +The examples directory contains client and server that are linked to +those crypto helper libraries and TLS backends. They are only built +if their corresponding crypto helper library is built: + +- client: OpenSSL client +- server: OpenSSL server +- gtlsclient: GnuTLS client +- gtlsserver: GnuTLS server +- bsslclient: BoringSSL client +- bsslserver: BoringSSL server +- ptlsclient: Picotls client +- ptlsserver: Picotls server +- wsslclient: wolfSSL client +- wsslserver: wolfSSL server + +QUIC protocol extensions +------------------------- + +The library implements the following QUIC protocol extensions: + +- `An Unreliable Datagram Extension to QUIC + <https://datatracker.ietf.org/doc/html/rfc9221>`_ +- `Greasing the QUIC Bit + <https://datatracker.ietf.org/doc/html/rfc9287>`_ +- `Compatible Version Negotiation for QUIC + <https://datatracker.ietf.org/doc/html/draft-ietf-quic-version-negotiation>`_ +- `QUIC Version 2 + <https://datatracker.ietf.org/doc/html/draft-ietf-quic-v2>`_ + +Configuring Wireshark for QUIC +------------------------------ + +`Wireshark <https://www.wireshark.org/download.html>`_ can be configured to +analyze QUIC traffic using the following steps: + +1. Set *SSLKEYLOGFILE* environment variable: + + .. code-block:: text + + $ export SSLKEYLOGFILE=quic_keylog_file + +2. Set the port that QUIC uses + + Go to *Preferences->Protocols->QUIC* and set the port the program + listens to. In the case of the example application this would be + the port specified on the command line. + +3. Set Pre-Master-Secret logfile + + Go to *Preferences->Protocols->TLS* and set the *Pre-Master-Secret + log file* to the same value that was specified for *SSLKEYLOGFILE*. + +4. Choose the correct network interface for capturing + + Make sure you choose the correct network interface for + capturing. For example, if using localhost choose the *loopback* + network interface on macos. + +5. Create a filter + + Create A filter for the udp.port and set the port to the port the + application is listening to. For example: + + .. code-block:: text + + udp.port == 7777 + +License +------- + +The MIT License + +Copyright (c) 2016 ngtcp2 contributors diff --git a/ci/build_boringssl.sh b/ci/build_boringssl.sh new file mode 100755 index 0000000..1a1b77c --- /dev/null +++ b/ci/build_boringssl.sh @@ -0,0 +1,10 @@ +#!/bin/sh -e +# build boringssl (for GitHub workflow) + +git clone https://boringssl.googlesource.com/boringssl +cd boringssl +git checkout 31bad2514d21f6207f3925ba56754611c462a873 +mkdir build +cd build +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. +make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" diff --git a/ci/build_nghttp3.sh b/ci/build_nghttp3.sh new file mode 100755 index 0000000..1e308ba --- /dev/null +++ b/ci/build_nghttp3.sh @@ -0,0 +1,9 @@ +#!/bin/sh -e +# build nghttp3 (for GitHub workflow) + +git clone https://github.com/ngtcp2/nghttp3 +cd nghttp3 +autoreconf -i +./configure --prefix=$PWD/build --enable-lib-only +make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" check +make install diff --git a/ci/build_openssl1.sh b/ci/build_openssl1.sh new file mode 100755 index 0000000..e350b36 --- /dev/null +++ b/ci/build_openssl1.sh @@ -0,0 +1,8 @@ +#!/bin/sh -e +# build patched openssl (for GitHub workflow) + +git clone --depth 1 -b OpenSSL_1_1_1s+quic https://github.com/quictls/openssl +cd openssl +./config --prefix=$PWD/build +make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" +make install_sw diff --git a/ci/build_openssl1_cross.sh b/ci/build_openssl1_cross.sh new file mode 100755 index 0000000..d11e365 --- /dev/null +++ b/ci/build_openssl1_cross.sh @@ -0,0 +1,9 @@ +#!/bin/sh -e +# build patched openssl (for GitHub workflow) for $HOST and $OSCC +# (os/compiler). + +git clone --depth 1 -b OpenSSL_1_1_1s+quic https://github.com/quictls/openssl +cd openssl +./Configure --cross-compile-prefix="$HOST"- --prefix=$PWD/build "$OSCC" +make -j$(nproc) +make install_sw diff --git a/ci/build_openssl3.sh b/ci/build_openssl3.sh new file mode 100755 index 0000000..0f517c6 --- /dev/null +++ b/ci/build_openssl3.sh @@ -0,0 +1,8 @@ +#!/bin/sh -e +# build patched openssl (for GitHub workflow) + +git clone --depth 1 -b openssl-3.0.7+quic https://github.com/quictls/openssl +cd openssl +./config --prefix=$PWD/build --openssldir=/etc/ssl +make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" +make install_sw diff --git a/ci/build_picotls.sh b/ci/build_picotls.sh new file mode 100755 index 0000000..3b85ff9 --- /dev/null +++ b/ci/build_picotls.sh @@ -0,0 +1,11 @@ +#!/bin/sh -e +# build picotls (for GitHub workflow) + +git clone https://github.com/h2o/picotls/ +cd picotls +git checkout 5e01b2b94dc77c500ed4fc0eaa77bd0cbe8e9274 +git submodule update --init +mkdir build +cd build +PKG_CONFIG_PATH=$PWD/../../openssl/build/lib/pkgconfig:$PWD/../../openssl/build/lib64/pkgconfig cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. +make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" diff --git a/ci/build_wolfssl.sh b/ci/build_wolfssl.sh new file mode 100755 index 0000000..3d179ed --- /dev/null +++ b/ci/build_wolfssl.sh @@ -0,0 +1,9 @@ +#!/bin/sh -e +# wolfssl (for GitHub workflow) + +git clone --depth 1 https://github.com/wolfSSL/wolfssl +cd wolfssl +autoreconf -i +./configure --prefix=$PWD/build --enable-all --enable-quic +make -j"$(nproc 2> /dev/null || sysctl -n hw.ncpu)" +make install diff --git a/ci/gen-certificate.sh b/ci/gen-certificate.sh new file mode 100755 index 0000000..e2c9e77 --- /dev/null +++ b/ci/gen-certificate.sh @@ -0,0 +1,8 @@ +#!/bin/sh -e +# Generate a self-signed certificate for testing purposes. + +mkdir -p cert +keyfile=cert/server.key +certfile=cert/server.crt + +openssl req -newkey rsa:2048 -x509 -nodes -keyout "$keyfile" -new -out "$certfile" -subj /CN=localhost 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/FindJemalloc.cmake b/cmake/FindJemalloc.cmake new file mode 100644 index 0000000..b7815fa --- /dev/null +++ b/cmake/FindJemalloc.cmake @@ -0,0 +1,40 @@ +# - Try to find jemalloc +# Once done this will define +# JEMALLOC_FOUND - System has jemalloc +# JEMALLOC_INCLUDE_DIRS - The jemalloc include directories +# JEMALLOC_LIBRARIES - The libraries needed to use jemalloc + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_JEMALLOC QUIET jemalloc) + +find_path(JEMALLOC_INCLUDE_DIR + NAMES jemalloc/jemalloc.h + HINTS ${PC_JEMALLOC_INCLUDE_DIRS} +) +find_library(JEMALLOC_LIBRARY + NAMES jemalloc + HINTS ${PC_JEMALLOC_LIBRARY_DIRS} +) + +if(JEMALLOC_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+JEMALLOC_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${JEMALLOC_INCLUDE_DIR}/jemalloc/jemalloc.h" + JEMALLOC_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + JEMALLOC_VERSION "${JEMALLOC_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set JEMALLOC_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Jemalloc REQUIRED_VARS + JEMALLOC_LIBRARY JEMALLOC_INCLUDE_DIR + VERSION_VAR JEMALLOC_VERSION) + +if(JEMALLOC_FOUND) + set(JEMALLOC_LIBRARIES ${JEMALLOC_LIBRARY}) + set(JEMALLOC_INCLUDE_DIRS ${JEMALLOC_INCLUDE_DIR}) +endif() + +mark_as_advanced(JEMALLOC_INCLUDE_DIR JEMALLOC_LIBRARY) diff --git a/cmake/FindLibev.cmake b/cmake/FindLibev.cmake new file mode 100644 index 0000000..71e4508 --- /dev/null +++ b/cmake/FindLibev.cmake @@ -0,0 +1,38 @@ +# - Try to find libev +# Once done this will define +# LIBEV_FOUND - System has libev +# LIBEV_INCLUDE_DIRS - The libev include directories +# LIBEV_LIBRARIES - The libraries needed to use libev + +find_path(LIBEV_INCLUDE_DIR + NAMES ev.h +) +find_library(LIBEV_LIBRARY + NAMES ev +) + +if(LIBEV_INCLUDE_DIR) + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MAJOR REGEX "^#define[ \t]+EV_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${LIBEV_INCLUDE_DIR}/ev.h" + LIBEV_VERSION_MINOR REGEX "^#define[ \t]+EV_VERSION_MINOR[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MAJOR "${LIBEV_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" LIBEV_VERSION_MINOR "${LIBEV_VERSION_MINOR}") + set(LIBEV_VERSION "${LIBEV_VERSION_MAJOR}.${LIBEV_VERSION_MINOR}") + unset(LIBEV_VERSION_MINOR) + unset(LIBEV_VERSION_MAJOR) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBEV_FOUND to TRUE +# if all listed variables are TRUE and the requested version matches. +find_package_handle_standard_args(Libev REQUIRED_VARS + LIBEV_LIBRARY LIBEV_INCLUDE_DIR + VERSION_VAR LIBEV_VERSION) + +if(LIBEV_FOUND) + set(LIBEV_LIBRARIES ${LIBEV_LIBRARY}) + set(LIBEV_INCLUDE_DIRS ${LIBEV_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBEV_INCLUDE_DIR LIBEV_LIBRARY) diff --git a/cmake/FindLibnghttp3.cmake b/cmake/FindLibnghttp3.cmake new file mode 100644 index 0000000..ecd01f6 --- /dev/null +++ b/cmake/FindLibnghttp3.cmake @@ -0,0 +1,41 @@ +# - Try to find libnghttp3 +# Once done this will define +# LIBNGHTTP3_FOUND - System has libnghttp3 +# LIBNGHTTP3_INCLUDE_DIRS - The libnghttp3 include directories +# LIBNGHTTP3_LIBRARIES - The libraries needed to use libnghttp3 + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBNGHTTP3 QUIET libnghttp3) + +find_path(LIBNGHTTP3_INCLUDE_DIR + NAMES nghttp3/nghttp3.h + HINTS ${PC_LIBNGHTTP3_INCLUDE_DIRS} +) +find_library(LIBNGHTTP3_LIBRARY + NAMES nghttp3 + HINTS ${PC_LIBNGHTTP3_LIBRARY_DIRS} +) + +if(LIBNGHTTP3_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+NGHTTP3_VERSION[ \t]+\"([^\"]+)\".*") + file(STRINGS "${LIBNGHTTP3_INCLUDE_DIR}/nghttp3/version.h" + LIBNGHTTP3_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + LIBNGHTTP3_VERSION "${LIBNGHTTP3_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set LIBNGHTTP3_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(Libnghttp3 REQUIRED_VARS + LIBNGHTTP3_LIBRARY LIBNGHTTP3_INCLUDE_DIR + VERSION_VAR LIBNGHTTP3_VERSION) + +if(LIBNGHTTP3_FOUND) + set(LIBNGHTTP3_LIBRARIES ${LIBNGHTTP3_LIBRARY}) + set(LIBNGHTTP3_INCLUDE_DIRS ${LIBNGHTTP3_INCLUDE_DIR}) +endif() + +mark_as_advanced(LIBNGHTTP3_INCLUDE_DIR LIBNGHTTP3_LIBRARY) diff --git a/cmake/Findwolfssl.cmake b/cmake/Findwolfssl.cmake new file mode 100644 index 0000000..47e1bba --- /dev/null +++ b/cmake/Findwolfssl.cmake @@ -0,0 +1,41 @@ +# - Try to find wolfssl +# Once done this will define +# WOLFSSL_FOUND - System has wolfssl +# WOLFSSL_INCLUDE_DIR - The wolfssl include directories +# WOLFSSL_LIBRARIES - The libraries needed to use wolfssl + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_WOLFSSL QUIET wolfssl) + +find_path(WOLFSSL_INCLUDE_DIR + NAMES wolfssl/ssl.h + HINTS ${PC_WOLFSSL_INCLUDE_DIRS} +) +find_library(WOLFSSL_LIBRARY + NAMES wolfssl + HINTS ${PC_WOLFSSL_LIBRARY_DIRS} +) + +if(WOLFSSL_INCLUDE_DIR) + set(_version_regex "^#define[ \t]+LIBWOLFSSL_VERSION_STRING[ \t]+\"([^\"]+)\".*") + file(STRINGS "${WOLFSSL_INCLUDE_DIR}/wolfssl/version.h" + WOLFSSL_VERSION REGEX "${_version_regex}") + string(REGEX REPLACE "${_version_regex}" "\\1" + WOLFSSL_VERSION "${WOLFSSL_VERSION}") + unset(_version_regex) +endif() + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set WOLFSSL_FOUND +# to TRUE if all listed variables are TRUE and the requested version +# matches. +find_package_handle_standard_args(wolfssl REQUIRED_VARS + WOLFSSL_LIBRARY WOLFSSL_INCLUDE_DIR + VERSION_VAR WOLFSSL_VERSION) + +if(WOLFSSL_FOUND) + set(WOLFSSL_LIBRARIES ${WOLFSSL_LIBRARY}) + set(WOLFSSL_INCLUDE_DIRS ${WOLFSSL_INCLUDE_DIR}) +endif() + +mark_as_advanced(WOLFSSL_INCLUDE_DIR WOLFSSL_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..011f9b9 --- /dev/null +++ b/cmakeconfig.h.in @@ -0,0 +1,36 @@ + +/* 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 <netinet/in.h> header file. */ +#cmakedefine HAVE_NETINET_IN_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..c40a98e --- /dev/null +++ b/configure.ac @@ -0,0 +1,755 @@ +# ngtcp2 +# +# 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([ngtcp2], [0.12.1], [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, 9) +AC_SUBST(LT_REVISION, 1) +AC_SUBST(LT_AGE, 0) + +AC_SUBST(CRYPTO_OPENSSL_LT_CURRENT, 3) +AC_SUBST(CRYPTO_OPENSSL_LT_REVISION, 0) +AC_SUBST(CRYPTO_OPENSSL_LT_AGE, 1) + +AC_SUBST(CRYPTO_GNUTLS_LT_CURRENT, 3) +AC_SUBST(CRYPTO_GNUTLS_LT_REVISION, 0) +AC_SUBST(CRYPTO_GNUTLS_LT_AGE, 1) + +AC_SUBST(CRYPTO_WOLFSSL_LT_CURRENT, 0) +AC_SUBST(CRYPTO_WOLFSSL_LT_REVISION, 1) +AC_SUBST(CRYPTO_WOLFSSL_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 from ngtcp2 +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([mempool], + [AS_HELP_STRING([--enable-mempool], [Turn on memory pool [default=yes]])], + [mempool=$enableval], [mempool=yes]) + +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 libngtcp2 and libngtcp2_crypto only.])], + [lib_only=$enableval], [lib_only=no]) + +AC_ARG_WITH([jemalloc], + [AS_HELP_STRING([--with-jemalloc], + [Use jemalloc [default=check]])], + [request_jemalloc=$withval], [request_jemalloc=check]) + +AC_ARG_WITH([cunit], + [AS_HELP_STRING([--with-cunit], + [Use cunit [default=check]])], + [request_cunit=$withval], [request_cunit=check]) + +AC_ARG_WITH([libnghttp3], + [AS_HELP_STRING([--with-libnghttp3], + [Use libnghttp3 [default=check]])], + [request_libnghttp3=$withval], [request_libnghttp3=check]) + +AC_ARG_WITH([libev], + [AS_HELP_STRING([--with-libev], + [Use libev [default=check]])], + [request_libev=$withval], [request_libev=check]) + +AC_ARG_WITH([openssl], + [AS_HELP_STRING([--with-openssl], + [Use openssl [default=check]])], + [request_openssl=$withval], [request_openssl=check]) + +AC_ARG_WITH([gnutls], + [AS_HELP_STRING([--with-gnutls], + [Use gnutls [default=no]])], + [request_gnutls=$withval], [request_gnutls=no]) + +AC_ARG_WITH([boringssl], + [AS_HELP_STRING([--with-boringssl], + [Use boringssl [default=no]])], + [request_boringssl=$withval], [request_boringssl=no]) + +AC_ARG_WITH([picotls], + [AS_HELP_STRING([--with-picotls], + [Use picotls [default=no]])], + [request_picotls=$withval], [request_picotls=no]) + +AC_ARG_WITH([wolfssl], + [AS_HELP_STRING([--with-wolfssl], + [Use wolfSSL [default=no]])], + [request_wolfssl=$withval], [request_wolfssl=no]) + +AC_ARG_VAR([BORINGSSL_CFLAGS], [C compiler flags for BORINGSSL]) +AC_ARG_VAR([BORINGSSL_LIBS], [linker flags for BORINGSSL]) + +AC_ARG_VAR([PICOTLS_CFLAGS], [C compiler flags for PICOTLS]) +AC_ARG_VAR([PICOTLS_LIBS], [linker flags for PICOTLS]) + +AC_ARG_VAR([WOLFSSL_CFLAGS], [C compiler flags for WOLFSSL]) +AC_ARG_VAR([WOLFSSL_LIBS], [linker flags for WOLFSSL]) + +AC_ARG_VAR([LIBEV_CFLAGS], [C compiler flags for libev, skipping any checks]) +AC_ARG_VAR([LIBEV_LIBS], [linker flags for libev, skipping any checks]) + +AC_ARG_VAR([JEMALLOC_CFLAGS], + [C compiler flags for jemalloc, skipping any checks]) +AC_ARG_VAR([JEMALLOC_LIBS], [linker flags for jemalloc, skipping any checks]) + +AC_ARG_VAR([LIBTOOL_LDFLAGS], + [libtool specific flags (e.g., -static-libtool-libs)]) + +# 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([20], [noext], [optional]) + +case "${build}" in + *-apple-darwin*) + EXTRA_DEFS="-D__APPLE_USE_RFC_3542" + AC_SUBST([EXTRA_DEFS]) + ;; +esac + +# 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" ]) + +AM_CONDITIONAL([ENABLE_SHARED], [ test "x${enable_shared}" = "xyes" ]) + +# OpenSSL (required for libngtcp2_crypto_openssl, +# libngtcp2_crypto_picotls and examples) +have_openssl=no +have_vanilla_openssl=no +if test "x${request_openssl}" != "xno"; then + PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.1.1], + [have_openssl=yes], [have_openssl=no]) + if test "x${have_openssl}" = "xno"; then + AC_MSG_NOTICE($OPENSSL_PKG_ERRORS) + else + have_vanilla_openssl=yes + VANILLA_OPENSSL_LIBS="$OPENSSL_LIBS" + VANILLA_OPENSSL_CFLAGS="$OPENSSL_CFLAGS" + + AC_SUBST(VANILLA_OPENSSL_LIBS) + AC_SUBST(VANILLA_OPENSSL_CFLAGS) + + # Until OpenSSL gains mainline support for QUIC, check for a + # patched version. + + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + CFLAGS="$OPENSSL_CFLAGS $CFLAGS" + LIBS="$OPENSSL_LIBS $LIBS" + + AC_MSG_CHECKING([for SSL_is_quic]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include <openssl/ssl.h> + ]], [[ + SSL *ssl = NULL; + SSL_is_quic(ssl); + ]])], + [AC_MSG_RESULT([yes]); have_openssl_quic=yes], + [AC_MSG_RESULT([no]); have_openssl_quic=no]) + + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" + + if test "x${have_openssl_quic}" = "xno"; then + AC_MSG_NOTICE([openssl does not have QUIC interface, disabling it]) + have_openssl=no + OPENSSL_LIBS= + OPENSSL_CFLAGS= + fi + fi +fi + +if test "x${request_openssl}" = "xyes" && + test "x${have_openssl}" != "xyes"; then + AC_MSG_ERROR([openssl was requested (--with-openssl) but not found]) +fi + +AM_CONDITIONAL([HAVE_OPENSSL], [ test "x${have_openssl}" = "xyes" ]) + +# GnuTLS (required for libngtcp2_crypto_gnutls) +have_gnutls=no +if test "x${request_gnutls}" != "xno"; then + PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.7.2], + [have_gnutls=yes], [have_gnutls=no]) + if test "x${have_gnutls}" = "xno"; then + AC_MSG_NOTICE($GNUTLS_PKG_ERRORS) + fi +fi + +if test "x${request_gnutls}" = "xyes" && + test "x${have_gnutls}" != "xyes"; then + AC_MSG_ERROR([gnutls was requested (--with-gnutls) but not found]) +fi + +AM_CONDITIONAL([HAVE_GNUTLS], [ test "x${have_gnutls}" = "xyes" ]) + +# BoringSSL (required for libngtcp2_crypto_boringssl) +have_boringssl=no +if test "x${request_boringssl}" != "xno"; then + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + CFLAGS="$BORINGSSL_CFLAGS $CFLAGS" + LIBS="$BORINGSSL_LIBS $LIBS" + + AC_MSG_CHECKING([for SSL_set_quic_early_data_context]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include <openssl/ssl.h> + ]], [[ + SSL *ssl = NULL; + SSL_set_quic_early_data_context(ssl, NULL, 0); + ]])], + [AC_MSG_RESULT([yes]); have_boringssl=yes], + [AC_MSG_RESULT([no]); have_boringssl=no]) + + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" +fi + +if test "x${request_boringssl}" = "xyes" && + test "x${have_boringssl}" != "xyes"; then + AC_MSG_ERROR([boringssl was requested (--with-boringssl) but not found]) +fi + +AM_CONDITIONAL([HAVE_BORINGSSL], [ test "x${have_boringssl}" = "xyes" ]) + +# Picotls openssl backend (required for libngtcp2_crypto_picotls) +have_picotls=no +if test "x${request_picotls}" != "xno"; then + save_CFLAGS="$CFLAGS" + save_LIBS="$LIBS" + CFLAGS="$PICOTLS_CFLAGS $VANILLA_OPENSSL_CFLAGS $CFLAGS" + LIBS="$PICOTLS_LIBS $VANILLA_OPENSSL_LIBS $LIBS" + + AC_MSG_CHECKING([for ptls_openssl_random_bytes]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include <picotls.h> + #include <picotls/openssl.h> + ]], [[ + ptls_openssl_random_bytes(NULL, 0); + ]])], + [AC_MSG_RESULT([yes]); have_picotls=yes], + [AC_MSG_RESULT([no]); have_picotls=no]) + + CFLAGS="$save_CFLAGS" + LIBS="$save_LIBS" +fi + +if test "x${request_picotls}" = "xyes" && + test "x${have_picotls}" != "xyes"; then + AC_MSG_ERROR([picotls was requested (--with-picotls) but not found]) +fi + +AM_CONDITIONAL([HAVE_PICOTLS], [ test "x${have_picotls}" = "xyes" ]) + +# wolfSSL (required for libngtcp2_crypto_wolfssl) +have_wolfssl=no +if test "x${request_wolfssl}" != "xno"; then + PKG_CHECK_MODULES([WOLFSSL], [wolfssl >= 5.5.0], + [have_wolfssl=yes], [have_wolfssl=no]) + if test "x${have_wolfssl}" = "xno"; then + AC_MSG_NOTICE($WOLFSSL_PKG_ERRORS) + fi +fi + +if test "x${request_wolfssl}" = "xyes" && + test "x${have_wolfssl}" != "xyes"; then + AC_MSG_ERROR([wolfssl was requested (--with-wolfssl) but not found]) +fi + +AM_CONDITIONAL([HAVE_WOLFSSL], [ test "x${have_wolfssl}" = "xyes" ]) + +have_crypto=no +if test "x${have_openssl}" = "xyes" || + test "x${have_gnutls}" = "xyes" || + test "x${have_boringssl}" = "xyes" || + test "x${have_picotls}" = "xyes" || + test "x${have_wolfssl}" = "xyes"; then + have_crypto=yes +fi + +AM_CONDITIONAL([HAVE_CRYPTO], [ test "x${have_crypto}" = "xyes" ]) + +# libnghttp3 (required for examples) +have_libnghttp3=no +if test "x${request_libnghttp3}" != "xno"; then + PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 0.2.0], + [have_libnghttp3=yes], [have_libnghttp3=no]) + if test "${have_libnghttp3}" = "xno"; then + AC_MSG_NOTICE($LIBNGHTTP3_PKG_ERRORS) + fi +fi + +if test "x${request_libnghttp3}" = "xyes" && + test "x${have_libnghttp3}" != "xyes"; then + AC_MSG_ERROR([libnghttp3 was requested (--with-libnghttp3) but not found]) +fi + +AM_CONDITIONAL([HAVE_NGHTTP3], [ test "x${have_libnghttp3}" = "xyes" ]) + +# libev (required for examples) +have_libev=no +if test "x${request_libev}" != "xno"; then + if test "x${LIBEV_LIBS}" = "x" && test "x${LIBEV_CFLAGS}" = "x"; then + # libev does not have pkg-config file. Check it in an old way. + save_LIBS=$LIBS + # android requires -lm for floor + AC_CHECK_LIB([ev], [ev_time], [have_libev=yes], [have_libev=no], [-lm]) + if test "x${have_libev}" = "xyes"; then + AC_CHECK_HEADER([ev.h], [have_libev=yes], [have_libev=no]) + if test "x${have_libev}" = "xyes"; then + LIBEV_LIBS=-lev + LIBEV_CFLAGS= + fi + fi + LIBS=$save_LIBS + else + have_libev=yes + fi +fi + +if test "x${request_libev}" = "xyes" && + test "x${have_libev}" != "xyes"; then + AC_MSG_ERROR([libev was requested (--with-libev) but not found]) +fi + +if test "x${lib_only}" = "xno" && + test "x${HAVE_CXX20}" != "x1"; then + AC_MSG_WARN([C++ compiler is not capable of C++20. Examples will not be built.]) +fi + +enable_examples=no +if test "x${lib_only}" = "xno" && + test "x${have_libnghttp3}" = "xyes" && + test "x${have_crypto}" = "xyes" && + test "x${have_libev}" = "xyes" && + test "x${HAVE_CXX20}" = "x1"; then + enable_examples=yes + + if test "x${have_openssl}" = "xyes"; then + AC_DEFINE([ENABLE_EXAMPLE_OPENSSL], [1], + [Define to 1 in order to build examples/{client,server}]) + fi + + if test "x${have_gnutls}" = "xyes"; then + AC_DEFINE([ENABLE_EXAMPLE_GNUTLS], [1], + [Define to 1 in order to build examples/{gtlsclient,gtlsserver}]) + fi + + if test "x${have_boringssl}" = "xyes"; then + AC_DEFINE([ENABLE_EXAMPLE_BORINGSSL], [1], + [Define to 1 in order to build examples/{bsslclient,bsslserver}]) + fi + + if test "x${have_picotls}" = "xyes"; then + AC_DEFINE([ENABLE_EXAMPLE_PICOTLS], [1], + [Define to 1 in order to build examples/{ptlsclient,ptlsserver}]) + fi + + if test "x${have_wolfssl}" = "xyes"; then + AC_DEFINE([ENABLE_EXAMPLE_WOLFSSL], [1], + [Define to 1 in order to build examples/{wsslclient,wsslserver}]) + fi +fi + +AM_CONDITIONAL([ENABLE_EXAMPLES], [ test "x${enable_examples}" = "xyes" ]) +AM_CONDITIONAL([ENABLE_EXAMPLE_OPENSSL], [ test "x${enable_examples}" = "xyes" && test "x${have_openssl}" = "xyes" ]) +AM_CONDITIONAL([ENABLE_EXAMPLE_GNUTLS], [ test "x${enable_examples}" = "xyes" && test "x${have_gnutls}" = "xyes" ]) +AM_CONDITIONAL([ENABLE_EXAMPLE_BORINGSSL], [ test "x${enable_examples}" = "xyes" && test "x${have_boringssl}" = "xyes" ]) +AM_CONDITIONAL([ENABLE_EXAMPLE_PICOTLS], [ test "x${enable_examples}" = "xyes" && test "x${have_picotls}" = "xyes" ]) +AM_CONDITIONAL([ENABLE_EXAMPLE_WOLFSSL], [ test "x${enable_examples}" = "xyes" && test "x${have_wolfssl}" = "xyes" ]) + +AC_SUBST([EXAMPLES_ENABLED], "${enable_examples}") +AC_SUBST([EXAMPLES_OPENSSL], "${have_openssl}") +AC_SUBST([EXAMPLES_GNUTLS], "${have_gnutls}") +AC_SUBST([EXAMPLES_BORINGSSL], "${have_boringssl}") +AC_SUBST([EXAMPLES_PICOTLS], "${have_picotls}") +AC_SUBST([EXAMPLES_WOLFSSL], "${have_wolfssl}") + +# jemalloc +have_jemalloc=no +if test "x${request_jemalloc}" != "xno"; then + if test "x${JEMALLOC_LIBS}" = "x" && test "x${JEMALLOC_CFLAGS}" = "x"; then + save_LIBS=$LIBS + AC_SEARCH_LIBS([malloc_stats_print], [jemalloc], [have_jemalloc=yes], [], + [$PTHREAD_LDFLAGS]) + + if test "x${have_jemalloc}" = "xyes"; then + jemalloc_libs=${ac_cv_search_malloc_stats_print} + else + # On Darwin, malloc_stats_print is je_malloc_stats_print + AC_SEARCH_LIBS([je_malloc_stats_print], [jemalloc], [have_jemalloc=yes], + [], [$PTHREAD_LDFLAGS]) + + if test "x${have_jemalloc}" = "xyes"; then + jemalloc_libs=${ac_cv_search_je_malloc_stats_print} + fi + fi + + LIBS=$save_LIBS + + if test "x${have_jemalloc}" = "xyes" && + test "x${jemalloc_libs}" != "xnone required"; then + JEMALLOC_LIBS=${jemalloc_libs} + fi + else + have_jemalloc=yes + fi +fi + +if test "x${request_jemalloc}" = "xyes" && + test "x${have_jemalloc}" != "xyes"; then + AC_MSG_ERROR([jemalloc was requested (--with-jemalloc) but not found]) +fi + +# Checks for header files. +AC_CHECK_HEADERS([ \ + arpa/inet.h \ + netinet/in.h \ + stddef.h \ + stdint.h \ + stdlib.h \ + string.h \ + unistd.h \ + sys/endian.h \ + endian.h \ + byteswap.h \ + asm/types.h \ + linux/netlink.h \ + linux/rtnetlink.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"]) + 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. + save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -fsanitize=address" + AX_CHECK_COMPILE_FLAG([-fsanitize=address], + [CFLAGS="$CFLAGS -fsanitize=address"; CXXFLAGS="$CXXFLAGS -fsanitize=address"], + [LDFLAGS="$save_LDFLAGS"]) +fi + +if test "x${memdebug}" = "xyes"; then + AC_DEFINE([MEMDEBUG], [1], + [Define to 1 to enable memory allocation debug output.]) +fi + +if test "x${mempool}" != "xyes"; then + AC_DEFINE([NOMEMPOOL], [1], [Define to 1 to disable memory pool.]) +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/libngtcp2.pc + lib/includes/Makefile + lib/includes/ngtcp2/version.h + tests/Makefile + crypto/Makefile + crypto/openssl/Makefile + crypto/openssl/libngtcp2_crypto_openssl.pc + crypto/includes/Makefile + crypto/gnutls/Makefile + crypto/gnutls/libngtcp2_crypto_gnutls.pc + crypto/boringssl/Makefile + crypto/boringssl/libngtcp2_crypto_boringssl.pc + crypto/picotls/Makefile + crypto/picotls/libngtcp2_crypto_picotls.pc + crypto/wolfssl/Makefile + crypto/wolfssl/libngtcp2_crypto_wolfssl.pc + doc/Makefile + doc/source/conf.py + third-party/Makefile + examples/Makefile + examples/tests/config.ini +]) +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} + Libtool: + LIBTOOL_LDFLAGS: ${LIBTOOL_LDFLAGS} + Crypto helper libraries: + libngtcp2_crypto_openssl: ${have_openssl} + libngtcp2_crypto_gnutls: ${have_gnutls} + libngtcp2_crypto_boringssl: ${have_boringssl} + libngtcp2_crypto_picotls: ${have_picotls} + libngtcp2_crypto_wolfssl: ${have_wolfssl} + Test: + CUnit: ${have_cunit} (CFLAGS='${CUNIT_CFLAGS}' LIBS='${CUNIT_LIBS}') + Debug: + Debug: ${debug} (CFLAGS='${DEBUGCFLAGS}') + Libs: + OpenSSL: ${have_openssl} (CFLAGS='${OPENSSL_CFLAGS}' LIBS='${OPENSSL_LIBS}') + Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}') + Libnghttp3: ${have_libnghttp3} (CFLAGS='${LIBNGHTTP3_CFLAGS}' LIBS='${LIBNGHTTP3_LIBS}') + Jemalloc: ${have_jemalloc} (CFLAGS='${JEMALLOC_CFLAGS}' LIBS='${JEMALLOC_LIBS}') + GnuTLS: ${have_gnutls} (CFLAGS='${GNUTLS_CFLAGS}' LIBS='${GNUTLS_LIBS}') + BoringSSL: ${have_boringssl} (CFLAGS='${BORINGSSL_CFLAGS}' LIBS='${BORINGSSL_LIBS}') + Picotls: ${have_picotls} (CFLAGS='${PICOTLS_CFLAGS}' LIBS='${PICOTLS_LIBS}') + wolfSSL: ${have_wolfssl} (CFLAGS='${WOLFSSL_CFLAGS}' LIBS='${WOLFSSL_LIBS}') + Examples: ${enable_examples} +]) diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt new file mode 100644 index 0000000..6ac8c74 --- /dev/null +++ b/crypto/CMakeLists.txt @@ -0,0 +1,56 @@ +# ngtcp2 + +# Copyright (c) 2019 ngtcp2 + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if(HAVE_CRYPTO) + add_subdirectory(includes) +endif() + +if(HAVE_OPENSSL) + add_subdirectory(openssl) +elseif(ENABLE_OPENSSL) + message(WARNING "libngtcp2_crypto_openssl library is disabled due to lack of good OpenSSL") +endif() + +if(HAVE_GNUTLS) + add_subdirectory(gnutls) +elseif(ENABLE_GNUTLS) + message(WARNING "libngtcp2_crypto_gnutls library is disabled due to lack of good GnuTLS") +endif() + +if(HAVE_BORINGSSL) + add_subdirectory(boringssl) +elseif(ENABLE_BORINGSSL) + message(WARNING "libngtcp2_crypto_boringssl library is disabled due to lack of good BoringSSL") +endif() + +if(HAVE_PICOTLS) + add_subdirectory(picotls) +elseif(ENABLE_PICOTLS) + message(WARNING "libngtcp2_crypto_picotls library is disabled due to lack of good Picotls") +endif() + +if(HAVE_WOLFSSL) + add_subdirectory(wolfssl) +elseif(ENABLE_WOLFSSL) + message(WARNING "libngtcp2_crypto_wolfssl library is disabled due to lack of good wolfSSL") +endif() diff --git a/crypto/Makefile.am b/crypto/Makefile.am new file mode 100644 index 0000000..67c6d3b --- /dev/null +++ b/crypto/Makefile.am @@ -0,0 +1,49 @@ +# ngtcp2 + +# Copyright (c) 2019 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = + +if HAVE_CRYPTO +SUBDIRS += includes +endif + +if HAVE_OPENSSL +SUBDIRS += openssl +endif + +if HAVE_GNUTLS +SUBDIRS += gnutls +endif + +if HAVE_BORINGSSL +SUBDIRS += boringssl +endif + +if HAVE_PICOTLS +SUBDIRS += picotls +endif + +if HAVE_WOLFSSL +SUBDIRS += wolfssl +endif + +EXTRA_DIST = CMakeLists.txt diff --git a/crypto/boringssl/.gitignore b/crypto/boringssl/.gitignore new file mode 100644 index 0000000..3d57277 --- /dev/null +++ b/crypto/boringssl/.gitignore @@ -0,0 +1,2 @@ +/libngtcp2_crypto_boringssl.pc +/libngtcp2_crypto_boringssl.a diff --git a/crypto/boringssl/CMakeLists.txt b/crypto/boringssl/CMakeLists.txt new file mode 100644 index 0000000..ebaae3d --- /dev/null +++ b/crypto/boringssl/CMakeLists.txt @@ -0,0 +1,63 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +add_definitions(-DBUILDING_NGTCP2) + +set(ngtcp2_crypto_boringssl_SOURCES + boringssl.c + ../shared.c +) + +set(ngtcp2_crypto_boringssl_INCLUDE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto" + "${BORINGSSL_INCLUDE_DIRS}" +) + +foreach(name libngtcp2_crypto_boringssl.pc) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +if(ENABLE_STATIC_LIB) + # Public static library + add_library(ngtcp2_crypto_boringssl_static ${ngtcp2_crypto_boringssl_SOURCES}) + set_target_properties(ngtcp2_crypto_boringssl_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + C_VISIBILITY_PRESET hidden + ) + target_compile_definitions(ngtcp2_crypto_boringssl_static PUBLIC + "-DNGTCP2_STATICLIB") + target_include_directories(ngtcp2_crypto_boringssl_static PUBLIC + ${ngtcp2_crypto_boringssl_INCLUDE_DIRS}) + + install(TARGETS ngtcp2_crypto_boringssl_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") +endif() + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_boringssl.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/crypto/boringssl/Makefile.am b/crypto/boringssl/Makefile.am new file mode 100644 index 0000000..7743233 --- /dev/null +++ b/crypto/boringssl/Makefile.am @@ -0,0 +1,39 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG) +AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \ + -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \ + -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \ + @BORINGSSL_CFLAGS@ +AM_LDFLAGS = ${LIBTOOL_LDFLAGS} + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libngtcp2_crypto_boringssl.pc +DISTCLEANFILES = $(pkgconfig_DATA) + +lib_LIBRARIES = libngtcp2_crypto_boringssl.a + +libngtcp2_crypto_boringssl_a_SOURCES = boringssl.c ../shared.c ../shared.h diff --git a/crypto/boringssl/boringssl.c b/crypto/boringssl/boringssl.c new file mode 100644 index 0000000..8b650bd --- /dev/null +++ b/crypto/boringssl/boringssl.c @@ -0,0 +1,630 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <assert.h> +#include <string.h> + +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_boringssl.h> + +#include <openssl/ssl.h> +#include <openssl/evp.h> +#include <openssl/hkdf.h> +#include <openssl/aes.h> +#include <openssl/chacha.h> +#include <openssl/rand.h> + +#include "shared.h" + +typedef enum ngtcp2_crypto_boringssl_cipher_type { + NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128, + NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256, + NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20, +} ngtcp2_crypto_boringssl_cipher_type; + +typedef struct ngtcp2_crypto_boringssl_cipher { + ngtcp2_crypto_boringssl_cipher_type type; +} ngtcp2_crypto_boringssl_cipher; + +static ngtcp2_crypto_boringssl_cipher crypto_cipher_aes_128 = { + NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128, +}; + +static ngtcp2_crypto_boringssl_cipher crypto_cipher_aes_256 = { + NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256, +}; + +static ngtcp2_crypto_boringssl_cipher crypto_cipher_chacha20 = { + NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20, +}; + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)EVP_aead_aes_128_gcm()); +} + +ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + md->native_handle = (void *)EVP_sha256(); + return md; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)EVP_aead_aes_128_gcm()); + ctx->md.native_handle = (void *)EVP_sha256(); + ctx->hp.native_handle = (void *)&crypto_cipher_aes_128; + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead, + void *aead_native_handle) { + aead->native_handle = aead_native_handle; + aead->max_overhead = EVP_AEAD_max_overhead(aead->native_handle); + return aead; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)EVP_aead_aes_128_gcm()); +} + +static const EVP_AEAD *crypto_ssl_get_aead(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_CK_AES_128_GCM_SHA256: + return EVP_aead_aes_128_gcm(); + case TLS1_CK_AES_256_GCM_SHA384: + return EVP_aead_aes_256_gcm(); + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return EVP_aead_chacha20_poly1305(); + default: + return NULL; + } +} + +static uint64_t crypto_ssl_get_aead_max_encryption(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_CK_AES_128_GCM_SHA256: + case TLS1_CK_AES_256_GCM_SHA384: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM; + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305; + default: + return 0; + } +} + +static uint64_t crypto_ssl_get_aead_max_decryption_failure(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_CK_AES_128_GCM_SHA256: + case TLS1_CK_AES_256_GCM_SHA384: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM; + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305; + default: + return 0; + } +} + +static const ngtcp2_crypto_boringssl_cipher *crypto_ssl_get_hp(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_CK_AES_128_GCM_SHA256: + return &crypto_cipher_aes_128; + case TLS1_CK_AES_256_GCM_SHA384: + return &crypto_cipher_aes_256; + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return &crypto_cipher_chacha20; + default: + return NULL; + } +} + +static const EVP_MD *crypto_ssl_get_md(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_CK_AES_128_GCM_SHA256: + case TLS1_CK_CHACHA20_POLY1305_SHA256: + return EVP_sha256(); + case TLS1_CK_AES_256_GCM_SHA384: + return EVP_sha384(); + default: + return NULL; + } +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + SSL *ssl = tls_native_handle; + ngtcp2_crypto_aead_init(&ctx->aead, (void *)crypto_ssl_get_aead(ssl)); + ctx->md.native_handle = (void *)crypto_ssl_get_md(ssl); + ctx->hp.native_handle = (void *)crypto_ssl_get_hp(ssl); + ctx->max_encryption = crypto_ssl_get_aead_max_encryption(ssl); + ctx->max_decryption_failure = crypto_ssl_get_aead_max_decryption_failure(ssl); + return ctx; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + return ngtcp2_crypto_ctx_tls(ctx, tls_native_handle); +} + +static size_t crypto_md_hashlen(const EVP_MD *md) { + return (size_t)EVP_MD_size(md); +} + +size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) { + return crypto_md_hashlen(md->native_handle); +} + +static size_t crypto_aead_keylen(const EVP_AEAD *aead) { + return (size_t)EVP_AEAD_key_length(aead); +} + +size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) { + return crypto_aead_keylen(aead->native_handle); +} + +static size_t crypto_aead_noncelen(const EVP_AEAD *aead) { + return (size_t)EVP_AEAD_nonce_length(aead); +} + +size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) { + return crypto_aead_noncelen(aead->native_handle); +} + +int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + const EVP_AEAD *cipher = aead->native_handle; + size_t keylen = crypto_aead_keylen(cipher); + EVP_AEAD_CTX *actx; + + (void)noncelen; + + actx = EVP_AEAD_CTX_new(cipher, key, keylen, EVP_AEAD_DEFAULT_TAG_LENGTH); + if (actx == NULL) { + return -1; + } + + aead_ctx->native_handle = actx; + + return 0; +} + +int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + return ngtcp2_crypto_aead_ctx_encrypt_init(aead_ctx, aead, key, noncelen); +} + +void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) { + if (aead_ctx->native_handle) { + EVP_AEAD_CTX_free(aead_ctx->native_handle); + } +} + +typedef struct ngtcp2_crypto_boringssl_cipher_ctx { + ngtcp2_crypto_boringssl_cipher_type type; + union { + /* aes_key is an encryption key when type is either + NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128 or + NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256. */ + AES_KEY aes_key; + /* key contains an encryption key when type == + NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20. */ + uint8_t key[32]; + }; +} ngtcp2_crypto_boringssl_cipher_ctx; + +int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, + const ngtcp2_crypto_cipher *cipher, + const uint8_t *key) { + ngtcp2_crypto_boringssl_cipher *hp_cipher = cipher->native_handle; + ngtcp2_crypto_boringssl_cipher_ctx *ctx; + int rv; + (void)rv; + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + + ctx->type = hp_cipher->type; + cipher_ctx->native_handle = ctx; + + switch (hp_cipher->type) { + case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128: + rv = AES_set_encrypt_key(key, 128, &ctx->aes_key); + assert(0 == rv); + return 0; + case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256: + rv = AES_set_encrypt_key(key, 256, &ctx->aes_key); + assert(0 == rv); + return 0; + case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20: + memcpy(ctx->key, key, sizeof(ctx->key)); + return 0; + default: + assert(0); + abort(); + }; +} + +void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) { + if (!cipher_ctx->native_handle) { + return; + } + + free(cipher_ctx->native_handle); +} + +int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen) { + const EVP_MD *prf = md->native_handle; + size_t destlen = (size_t)EVP_MD_size(prf); + + if (HKDF_extract(dest, &destlen, prf, secret, secretlen, salt, saltlen) != + 1) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *info, + size_t infolen) { + const EVP_MD *prf = md->native_handle; + + if (HKDF_expand(dest, destlen, prf, secret, secretlen, info, infolen) != 1) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, size_t saltlen, + const uint8_t *info, size_t infolen) { + const EVP_MD *prf = md->native_handle; + + if (HKDF(dest, destlen, prf, secret, secretlen, salt, saltlen, info, + infolen) != 1) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + const EVP_AEAD *cipher = aead->native_handle; + EVP_AEAD_CTX *actx = aead_ctx->native_handle; + size_t max_outlen = plaintextlen + EVP_AEAD_max_overhead(cipher); + size_t outlen; + + if (EVP_AEAD_CTX_seal(actx, dest, &outlen, max_outlen, nonce, noncelen, + plaintext, plaintextlen, aad, aadlen) != 1) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + const EVP_AEAD *cipher = aead->native_handle; + EVP_AEAD_CTX *actx = aead_ctx->native_handle; + size_t max_overhead = EVP_AEAD_max_overhead(cipher); + size_t max_outlen; + size_t outlen; + + if (ciphertextlen < max_overhead) { + return -1; + } + + max_outlen = ciphertextlen - max_overhead; + + if (EVP_AEAD_CTX_open(actx, dest, &outlen, max_outlen, nonce, noncelen, + ciphertext, ciphertextlen, aad, aadlen) != 1) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; + ngtcp2_crypto_boringssl_cipher_ctx *ctx = hp_ctx->native_handle; + uint32_t counter; + + (void)hp; + + switch (ctx->type) { + case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_128: + case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_AES_256: + AES_ecb_encrypt(sample, dest, &ctx->aes_key, 1); + return 0; + case NGTCP2_CRYPTO_BORINGSSL_CIPHER_TYPE_CHACHA20: +#if defined(WORDS_BIGENDIAN) + counter = (uint32_t)sample[0] + (uint32_t)(sample[1] << 8) + + (uint32_t)(sample[2] << 16) + (uint32_t)(sample[3] << 24); +#else /* !WORDS_BIGENDIAN */ + memcpy(&counter, sample, sizeof(counter)); +#endif /* !WORDS_BIGENDIAN */ + CRYPTO_chacha_20(dest, PLAINTEXT, sizeof(PLAINTEXT) - 1, ctx->key, + sample + sizeof(counter), counter); + return 0; + default: + assert(0); + abort(); + } +} + +int ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen) { + SSL *ssl = ngtcp2_conn_get_tls_native_handle(conn); + int rv; + int err; + + if (SSL_provide_quic_data( + ssl, ngtcp2_crypto_boringssl_from_ngtcp2_crypto_level(crypto_level), + data, datalen) != 1) { + return -1; + } + + if (!ngtcp2_conn_get_handshake_completed(conn)) { + retry: + rv = SSL_do_handshake(ssl); + if (rv <= 0) { + err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_SSL: + return -1; + case SSL_ERROR_EARLY_DATA_REJECTED: + assert(!ngtcp2_conn_is_server(conn)); + + SSL_reset_early_data_reject(ssl); + + rv = ngtcp2_conn_early_data_rejected(conn); + if (rv != 0) { + return -1; + } + + goto retry; + default: + return -1; + } + } + + if (SSL_in_early_data(ssl)) { + return 0; + } + + ngtcp2_conn_handshake_completed(conn); + } + + rv = SSL_process_quic_post_handshake(ssl); + if (rv != 1) { + err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_SSL: + case SSL_ERROR_ZERO_RETURN: + return -1; + default: + return -1; + } + } + + return 0; +} + +int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) { + SSL *ssl = tls; + const uint8_t *tp; + size_t tplen; + int rv; + + SSL_get_peer_quic_transport_params(ssl, &tp, &tplen); + + rv = ngtcp2_conn_decode_remote_transport_params(conn, tp, tplen); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return -1; + } + + return 0; +} + +int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf, + size_t len) { + if (SSL_set_quic_transport_params(tls, buf, len) != 1) { + return -1; + } + + return 0; +} + +ngtcp2_crypto_level ngtcp2_crypto_boringssl_from_ssl_encryption_level( + enum ssl_encryption_level_t ssl_level) { + switch (ssl_level) { + case ssl_encryption_initial: + return NGTCP2_CRYPTO_LEVEL_INITIAL; + case ssl_encryption_early_data: + return NGTCP2_CRYPTO_LEVEL_EARLY; + case ssl_encryption_handshake: + return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + case ssl_encryption_application: + return NGTCP2_CRYPTO_LEVEL_APPLICATION; + default: + assert(0); + abort(); + } +} + +enum ssl_encryption_level_t ngtcp2_crypto_boringssl_from_ngtcp2_crypto_level( + ngtcp2_crypto_level crypto_level) { + switch (crypto_level) { + case NGTCP2_CRYPTO_LEVEL_INITIAL: + return ssl_encryption_initial; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + return ssl_encryption_handshake; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + return ssl_encryption_application; + case NGTCP2_CRYPTO_LEVEL_EARLY: + return ssl_encryption_early_data; + default: + assert(0); + abort(); + } +} + +int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + void *user_data) { + (void)conn; + (void)user_data; + + if (RAND_bytes(data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + if (RAND_bytes(data, datalen) != 1) { + return -1; + } + + return 0; +} + +static int set_read_secret(SSL *ssl, enum ssl_encryption_level_t bssl_level, + const SSL_CIPHER *cipher, const uint8_t *secret, + size_t secretlen) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = + ngtcp2_crypto_boringssl_from_ssl_encryption_level(bssl_level); + (void)cipher; + + if (ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level, + secret, secretlen) != 0) { + return 0; + } + + return 1; +} + +static int set_write_secret(SSL *ssl, enum ssl_encryption_level_t bssl_level, + const SSL_CIPHER *cipher, const uint8_t *secret, + size_t secretlen) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = + ngtcp2_crypto_boringssl_from_ssl_encryption_level(bssl_level); + (void)cipher; + + if (ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level, + secret, secretlen) != 0) { + return 0; + } + + return 1; +} + +static int add_handshake_data(SSL *ssl, enum ssl_encryption_level_t bssl_level, + const uint8_t *data, size_t datalen) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = + ngtcp2_crypto_boringssl_from_ssl_encryption_level(bssl_level); + int rv; + + rv = ngtcp2_conn_submit_crypto_data(conn, level, data, datalen); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return 0; + } + + return 1; +} + +static int flush_flight(SSL *ssl) { + (void)ssl; + return 1; +} + +static int send_alert(SSL *ssl, enum ssl_encryption_level_t bssl_level, + uint8_t alert) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + (void)bssl_level; + + ngtcp2_conn_set_tls_alert(conn, alert); + + return 1; +} + +static SSL_QUIC_METHOD quic_method = { + set_read_secret, set_write_secret, add_handshake_data, + flush_flight, send_alert, +}; + +static void crypto_boringssl_configure_context(SSL_CTX *ssl_ctx) { + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_quic_method(ssl_ctx, &quic_method); +} + +int ngtcp2_crypto_boringssl_configure_server_context(SSL_CTX *ssl_ctx) { + crypto_boringssl_configure_context(ssl_ctx); + + return 0; +} + +int ngtcp2_crypto_boringssl_configure_client_context(SSL_CTX *ssl_ctx) { + crypto_boringssl_configure_context(ssl_ctx); + + return 0; +} diff --git a/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in b/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in new file mode 100644 index 0000000..737970a --- /dev/null +++ b/crypto/boringssl/libngtcp2_crypto_boringssl.pc.in @@ -0,0 +1,33 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libngtcp2_crypto_boringssl +Description: ngtcp2 BoringSSL crypto library +URL: https://github.com/ngtcp2/ngtcp2 +Version: @VERSION@ +Libs: -L${libdir} -lngtcp2_crypto_boringssl +Cflags: -I${includedir} diff --git a/crypto/gnutls/.gitignore b/crypto/gnutls/.gitignore new file mode 100644 index 0000000..b11bf3d --- /dev/null +++ b/crypto/gnutls/.gitignore @@ -0,0 +1 @@ +/libngtcp2_crypto_gnutls.pc diff --git a/crypto/gnutls/CMakeLists.txt b/crypto/gnutls/CMakeLists.txt new file mode 100644 index 0000000..9b7f225 --- /dev/null +++ b/crypto/gnutls/CMakeLists.txt @@ -0,0 +1,85 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +add_definitions(-DBUILDING_NGTCP2) + +set(ngtcp2_crypto_gnutls_SOURCES + gnutls.c + ../shared.c +) + +set(ngtcp2_crypto_gnutls_INCLUDE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto" + "${GNUTLS_INCLUDE_DIRS}" +) + +foreach(name libngtcp2_crypto_gnutls.pc) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +# Public shared library +if(ENABLE_SHARED_LIB) + add_library(ngtcp2_crypto_gnutls SHARED ${ngtcp2_crypto_gnutls_SOURCES}) + set_target_properties(ngtcp2_crypto_gnutls PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${CRYPTO_GNUTLS_LT_VERSION} + SOVERSION ${CRYPTO_GNUTLS_LT_SOVERSION} + C_VISIBILITY_PRESET hidden + POSITION_INDEPENDENT_CODE ON + ) + target_include_directories(ngtcp2_crypto_gnutls PUBLIC + ${ngtcp2_crypto_gnutls_INCLUDE_DIRS}) + target_link_libraries(ngtcp2_crypto_gnutls ngtcp2 ${GNUTLS_LIBRARIES}) + + install(TARGETS ngtcp2_crypto_gnutls + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() + +if(ENABLE_STATIC_LIB) + # Public static library + add_library(ngtcp2_crypto_gnutls_static ${ngtcp2_crypto_gnutls_SOURCES}) + set_target_properties(ngtcp2_crypto_gnutls_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${CRYPTO_GNUTLS_LT_VERSION} + SOVERSION ${CRYPTO_GNUTLS_LT_SOVERSION} + C_VISIBILITY_PRESET hidden + ) + target_compile_definitions(ngtcp2_crypto_gnutls_static PUBLIC + "-DNGTCP2_STATICLIB") + target_include_directories(ngtcp2_crypto_gnutls_static PUBLIC + ${ngtcp2_crypto_gnutls_INCLUDE_DIRS}) + + install(TARGETS ngtcp2_crypto_gnutls_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") +endif() + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_gnutls.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/crypto/gnutls/Makefile.am b/crypto/gnutls/Makefile.am new file mode 100644 index 0000000..b4ed6c6 --- /dev/null +++ b/crypto/gnutls/Makefile.am @@ -0,0 +1,43 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG) +AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \ + -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \ + -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \ + @GNUTLS_CFLAGS@ +AM_LDFLAGS = ${LIBTOOL_LDFLAGS} + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libngtcp2_crypto_gnutls.pc +DISTCLEANFILES = $(pkgconfig_DATA) + +lib_LTLIBRARIES = libngtcp2_crypto_gnutls.la + +libngtcp2_crypto_gnutls_la_SOURCES = gnutls.c ../shared.c ../shared.h +libngtcp2_crypto_gnutls_la_LDFLAGS = -no-undefined \ + -version-info $(CRYPTO_GNUTLS_LT_CURRENT):$(CRYPTO_GNUTLS_LT_REVISION):$(CRYPTO_GNUTLS_LT_AGE) +libngtcp2_crypto_gnutls_la_LIBADD = $(top_builddir)/lib/libngtcp2.la \ + @GNUTLS_LIBS@ diff --git a/crypto/gnutls/gnutls.c b/crypto/gnutls/gnutls.c new file mode 100644 index 0000000..73ea0c1 --- /dev/null +++ b/crypto/gnutls/gnutls.c @@ -0,0 +1,644 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <assert.h> + +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_gnutls.h> + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <string.h> + +#include "shared.h" + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)GNUTLS_CIPHER_AES_128_GCM); +} + +ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + md->native_handle = (void *)GNUTLS_DIG_SHA256; + return md; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)GNUTLS_CIPHER_AES_128_GCM); + ctx->md.native_handle = (void *)GNUTLS_DIG_SHA256; + ctx->hp.native_handle = (void *)GNUTLS_CIPHER_AES_128_CBC; + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead, + void *aead_native_handle) { + aead->native_handle = aead_native_handle; + aead->max_overhead = gnutls_cipher_get_tag_size( + (gnutls_cipher_algorithm_t)(intptr_t)aead_native_handle); + return aead; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)GNUTLS_CIPHER_AES_128_GCM); +} + +static gnutls_cipher_algorithm_t +crypto_get_hp(gnutls_cipher_algorithm_t cipher) { + switch (cipher) { + case GNUTLS_CIPHER_AES_128_GCM: + case GNUTLS_CIPHER_AES_128_CCM: + return GNUTLS_CIPHER_AES_128_CBC; + case GNUTLS_CIPHER_AES_256_GCM: + case GNUTLS_CIPHER_AES_256_CCM: + return GNUTLS_CIPHER_AES_256_CBC; + case GNUTLS_CIPHER_CHACHA20_POLY1305: + return GNUTLS_CIPHER_CHACHA20_32; + default: + return GNUTLS_CIPHER_UNKNOWN; + } +} + +static uint64_t +crypto_get_aead_max_encryption(gnutls_cipher_algorithm_t cipher) { + switch (cipher) { + case GNUTLS_CIPHER_AES_128_GCM: + case GNUTLS_CIPHER_AES_256_GCM: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM; + case GNUTLS_CIPHER_CHACHA20_POLY1305: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305; + case GNUTLS_CIPHER_AES_128_CCM: + case GNUTLS_CIPHER_AES_256_CCM: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM; + default: + return 0; + } +} + +static uint64_t +crypto_get_aead_max_decryption_failure(gnutls_cipher_algorithm_t cipher) { + switch (cipher) { + case GNUTLS_CIPHER_AES_128_GCM: + case GNUTLS_CIPHER_AES_256_GCM: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM; + case GNUTLS_CIPHER_CHACHA20_POLY1305: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305; + case GNUTLS_CIPHER_AES_128_CCM: + case GNUTLS_CIPHER_AES_256_CCM: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM; + default: + return 0; + } +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + gnutls_session_t session = tls_native_handle; + gnutls_cipher_algorithm_t cipher; + gnutls_digest_algorithm_t hash; + gnutls_cipher_algorithm_t hp_cipher; + + cipher = gnutls_cipher_get(session); + if (cipher != GNUTLS_CIPHER_UNKNOWN && cipher != GNUTLS_CIPHER_NULL) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)cipher); + } + + hash = gnutls_prf_hash_get(session); + if (hash != GNUTLS_DIG_UNKNOWN && hash != GNUTLS_DIG_NULL) { + ctx->md.native_handle = (void *)hash; + } + + hp_cipher = crypto_get_hp(cipher); + if (hp_cipher != GNUTLS_CIPHER_UNKNOWN) { + ctx->hp.native_handle = (void *)hp_cipher; + } + + ctx->max_encryption = crypto_get_aead_max_encryption(cipher); + ctx->max_decryption_failure = crypto_get_aead_max_decryption_failure(cipher); + + return ctx; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + gnutls_session_t session = tls_native_handle; + gnutls_cipher_algorithm_t cipher; + gnutls_digest_algorithm_t hash; + gnutls_cipher_algorithm_t hp_cipher; + + cipher = gnutls_early_cipher_get(session); + if (cipher != GNUTLS_CIPHER_UNKNOWN && cipher != GNUTLS_CIPHER_NULL) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)cipher); + } + + hash = gnutls_early_prf_hash_get(session); + if (hash != GNUTLS_DIG_UNKNOWN && hash != GNUTLS_DIG_NULL) { + ctx->md.native_handle = (void *)hash; + } + + hp_cipher = crypto_get_hp(cipher); + if (hp_cipher != GNUTLS_CIPHER_UNKNOWN) { + ctx->hp.native_handle = (void *)hp_cipher; + } + + ctx->max_encryption = crypto_get_aead_max_encryption(cipher); + ctx->max_decryption_failure = crypto_get_aead_max_decryption_failure(cipher); + + return ctx; +} + +size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) { + return gnutls_hash_get_len( + (gnutls_digest_algorithm_t)(intptr_t)md->native_handle); +} + +size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) { + return gnutls_cipher_get_key_size( + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle); +} + +size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) { + return gnutls_cipher_get_iv_size( + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle); +} + +int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle; + gnutls_aead_cipher_hd_t hd; + gnutls_datum_t _key; + + (void)noncelen; + + _key.data = (void *)key; + _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead); + + if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0) { + return -1; + } + + aead_ctx->native_handle = hd; + + return 0; +} + +int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle; + gnutls_aead_cipher_hd_t hd; + gnutls_datum_t _key; + + (void)noncelen; + + _key.data = (void *)key; + _key.size = (unsigned int)ngtcp2_crypto_aead_keylen(aead); + + if (gnutls_aead_cipher_init(&hd, cipher, &_key) != 0) { + return -1; + } + + aead_ctx->native_handle = hd; + + return 0; +} + +void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) { + if (aead_ctx->native_handle) { + gnutls_aead_cipher_deinit(aead_ctx->native_handle); + } +} + +int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, + const ngtcp2_crypto_cipher *cipher, + const uint8_t *key) { + gnutls_cipher_algorithm_t _cipher = + (gnutls_cipher_algorithm_t)(intptr_t)cipher->native_handle; + gnutls_cipher_hd_t hd; + gnutls_datum_t _key; + + _key.data = (void *)key; + _key.size = (unsigned int)gnutls_cipher_get_key_size(_cipher); + + if (gnutls_cipher_init(&hd, _cipher, &_key, NULL) != 0) { + return -1; + } + + cipher_ctx->native_handle = hd; + + return 0; +} + +void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) { + if (cipher_ctx->native_handle) { + gnutls_cipher_deinit(cipher_ctx->native_handle); + } +} + +int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen) { + gnutls_mac_algorithm_t prf = + (gnutls_mac_algorithm_t)(intptr_t)md->native_handle; + gnutls_datum_t _secret = {(void *)secret, (unsigned int)secretlen}; + gnutls_datum_t _salt = {(void *)salt, (unsigned int)saltlen}; + + if (gnutls_hkdf_extract(prf, &_secret, &_salt, dest) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *info, + size_t infolen) { + gnutls_mac_algorithm_t prf = + (gnutls_mac_algorithm_t)(intptr_t)md->native_handle; + gnutls_datum_t _secret = {(void *)secret, (unsigned int)secretlen}; + gnutls_datum_t _info = {(void *)info, (unsigned int)infolen}; + + if (gnutls_hkdf_expand(prf, &_secret, &_info, dest, destlen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, size_t saltlen, + const uint8_t *info, size_t infolen) { + gnutls_mac_algorithm_t prf = + (gnutls_mac_algorithm_t)(intptr_t)md->native_handle; + size_t keylen = ngtcp2_crypto_md_hashlen(md); + uint8_t key[64]; + gnutls_datum_t _secret = {(void *)secret, (unsigned int)secretlen}; + gnutls_datum_t _key = {(void *)key, (unsigned int)keylen}; + gnutls_datum_t _salt = {(void *)salt, (unsigned int)saltlen}; + gnutls_datum_t _info = {(void *)info, (unsigned int)infolen}; + + assert(keylen <= sizeof(key)); + + if (gnutls_hkdf_extract(prf, &_secret, &_salt, key) != 0) { + return -1; + } + + if (gnutls_hkdf_expand(prf, &_key, &_info, dest, destlen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle; + gnutls_aead_cipher_hd_t hd = aead_ctx->native_handle; + size_t taglen = gnutls_cipher_get_tag_size(cipher); + size_t ciphertextlen = plaintextlen + taglen; + + if (gnutls_aead_cipher_encrypt(hd, nonce, noncelen, aad, aadlen, taglen, + plaintext, plaintextlen, dest, + &ciphertextlen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)aead->native_handle; + gnutls_aead_cipher_hd_t hd = aead_ctx->native_handle; + size_t taglen = gnutls_cipher_get_tag_size(cipher); + size_t plaintextlen; + + if (taglen > ciphertextlen) { + return -1; + } + + plaintextlen = ciphertextlen - taglen; + + if (gnutls_aead_cipher_decrypt(hd, nonce, noncelen, aad, aadlen, taglen, + ciphertext, ciphertextlen, dest, + &plaintextlen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + gnutls_cipher_algorithm_t cipher = + (gnutls_cipher_algorithm_t)(intptr_t)hp->native_handle; + gnutls_cipher_hd_t hd = hp_ctx->native_handle; + + switch (cipher) { + case GNUTLS_CIPHER_AES_128_CBC: + case GNUTLS_CIPHER_AES_256_CBC: { + uint8_t iv[16]; + uint8_t buf[16]; + + /* Emulate one block AES-ECB by invalidating the effect of IV */ + memset(iv, 0, sizeof(iv)); + + gnutls_cipher_set_iv(hd, iv, sizeof(iv)); + + if (gnutls_cipher_encrypt2(hd, sample, 16, buf, sizeof(buf)) != 0) { + return -1; + } + + memcpy(dest, buf, 5); + } break; + + case GNUTLS_CIPHER_CHACHA20_32: { + static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; + uint8_t buf[5 + 16]; + size_t buflen = sizeof(buf); + + gnutls_cipher_set_iv(hd, (void *)sample, 16); + + if (gnutls_cipher_encrypt2(hd, PLAINTEXT, sizeof(PLAINTEXT) - 1, buf, + buflen) != 0) { + return -1; + } + + memcpy(dest, buf, 5); + } break; + default: + assert(0); + } + + return 0; +} + +ngtcp2_crypto_level ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level( + gnutls_record_encryption_level_t gtls_level) { + switch (gtls_level) { + case GNUTLS_ENCRYPTION_LEVEL_INITIAL: + return NGTCP2_CRYPTO_LEVEL_INITIAL; + case GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE: + return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + case GNUTLS_ENCRYPTION_LEVEL_APPLICATION: + return NGTCP2_CRYPTO_LEVEL_APPLICATION; + case GNUTLS_ENCRYPTION_LEVEL_EARLY: + return NGTCP2_CRYPTO_LEVEL_EARLY; + default: + assert(0); + abort(); + } +} + +gnutls_record_encryption_level_t +ngtcp2_crypto_gnutls_from_ngtcp2_level(ngtcp2_crypto_level crypto_level) { + switch (crypto_level) { + case NGTCP2_CRYPTO_LEVEL_INITIAL: + return GNUTLS_ENCRYPTION_LEVEL_INITIAL; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + return GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + return GNUTLS_ENCRYPTION_LEVEL_APPLICATION; + case NGTCP2_CRYPTO_LEVEL_EARLY: + return GNUTLS_ENCRYPTION_LEVEL_EARLY; + default: + assert(0); + abort(); + } +} + +int ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen) { + gnutls_session_t session = ngtcp2_conn_get_tls_native_handle(conn); + int rv; + + if (datalen > 0) { + rv = gnutls_handshake_write( + session, ngtcp2_crypto_gnutls_from_ngtcp2_level(crypto_level), data, + datalen); + if (rv != 0) { + if (!gnutls_error_is_fatal(rv)) { + return 0; + } + gnutls_alert_send_appropriate(session, rv); + return -1; + } + } + + if (!ngtcp2_conn_get_handshake_completed(conn)) { + rv = gnutls_handshake(session); + if (rv < 0) { + if (!gnutls_error_is_fatal(rv)) { + return 0; + } + gnutls_alert_send_appropriate(session, rv); + return -1; + } + + ngtcp2_conn_handshake_completed(conn); + } + + return 0; +} + +int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) { + (void)conn; + (void)tls; + /* Nothing to do; GnuTLS applications are supposed to register the + quic_transport_parameters extension with + gnutls_session_ext_register. */ + return 0; +} + +int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf, + size_t len) { + (void)tls; + (void)buf; + (void)len; + /* Nothing to do; GnuTLS applications are supposed to register the + quic_transport_parameters extension with + gnutls_session_ext_register. */ + return 0; +} + +int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + void *user_data) { + (void)conn; + (void)user_data; + + if (gnutls_rnd(GNUTLS_RND_RANDOM, data, NGTCP2_PATH_CHALLENGE_DATALEN) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + if (gnutls_rnd(GNUTLS_RND_RANDOM, data, datalen) != 0) { + return -1; + } + + return 0; +} + +static int secret_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + const void *rx_secret, const void *tx_secret, + size_t secretlen) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = + ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level); + + if (rx_secret && + ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level, + rx_secret, secretlen) != 0) { + return -1; + } + + if (tx_secret && + ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level, + tx_secret, secretlen) != 0) { + return -1; + } + + return 0; +} + +static int read_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + gnutls_handshake_description_t htype, const void *data, + size_t datalen) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = + ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level(gtls_level); + int rv; + + if (htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC) { + return 0; + } + + rv = ngtcp2_conn_submit_crypto_data(conn, level, data, datalen); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return -1; + } + + return 0; +} + +static int alert_read_func(gnutls_session_t session, + gnutls_record_encryption_level_t gtls_level, + gnutls_alert_level_t alert_level, + gnutls_alert_description_t alert_desc) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + (void)gtls_level; + (void)alert_level; + + ngtcp2_conn_set_tls_alert(conn, (uint8_t)alert_desc); + + return 0; +} + +static int tp_recv_func(gnutls_session_t session, const uint8_t *data, + size_t datalen) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + int rv; + + rv = ngtcp2_conn_decode_remote_transport_params(conn, data, datalen); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return -1; + } + + return 0; +} + +static int tp_send_func(gnutls_session_t session, gnutls_buffer_t extdata) { + ngtcp2_crypto_conn_ref *conn_ref = gnutls_session_get_ptr(session); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + uint8_t buf[256]; + ngtcp2_ssize nwrite; + int rv; + + nwrite = ngtcp2_conn_encode_local_transport_params(conn, buf, sizeof(buf)); + if (nwrite < 0) { + return -1; + } + + rv = gnutls_buffer_append_data(extdata, buf, (size_t)nwrite); + if (rv != 0) { + return -1; + } + + return 0; +} + +static int crypto_gnutls_configure_session(gnutls_session_t session) { + int rv; + + gnutls_handshake_set_secret_function(session, secret_func); + gnutls_handshake_set_read_function(session, read_func); + gnutls_alert_set_read_function(session, alert_read_func); + + rv = gnutls_session_ext_register( + session, "QUIC Transport Parameters", + NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1, GNUTLS_EXT_TLS, tp_recv_func, + tp_send_func, NULL, NULL, NULL, + GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_EE); + if (rv != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_gnutls_configure_server_session(gnutls_session_t session) { + return crypto_gnutls_configure_session(session); +} + +int ngtcp2_crypto_gnutls_configure_client_session(gnutls_session_t session) { + return crypto_gnutls_configure_session(session); +} diff --git a/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in b/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in new file mode 100644 index 0000000..890e89d --- /dev/null +++ b/crypto/gnutls/libngtcp2_crypto_gnutls.pc.in @@ -0,0 +1,33 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libngtcp2_crypto_gnutls +Description: ngtcp2 GnuTLS crypto library +URL: https://github.com/ngtcp2/ngtcp2 +Version: @VERSION@ +Libs: -L${libdir} -lngtcp2_crypto_gnutls +Cflags: -I${includedir} diff --git a/crypto/includes/CMakeLists.txt b/crypto/includes/CMakeLists.txt new file mode 100644 index 0000000..10f9122 --- /dev/null +++ b/crypto/includes/CMakeLists.txt @@ -0,0 +1,56 @@ +# ngtcp2 + +# Copyright (c) 2019 ngtcp2 + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +install(FILES + ngtcp2/ngtcp2_crypto.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2") + +if(HAVE_OPENSSL) + install(FILES + ngtcp2/ngtcp2_crypto_openssl.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2") +endif() + +if(HAVE_GNUTLS) + install(FILES + ngtcp2/ngtcp2_crypto_gnutls.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2") +endif() + +if(HAVE_BORINGSSL) + install(FILES + ngtcp2/ngtcp2_crypto_boringssl.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2") +endif() + +if(HAVE_PICOTLS) + install(FILES + ngtcp2/ngtcp2_crypto_picotls.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2") +endif() + +if(HAVE_WOLFSSL) + install(FILES + ngtcp2/ngtcp2_crypto_wolfssl.h + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2") +endif() diff --git a/crypto/includes/Makefile.am b/crypto/includes/Makefile.am new file mode 100644 index 0000000..a688a20 --- /dev/null +++ b/crypto/includes/Makefile.am @@ -0,0 +1,45 @@ +# ngtcp2 + +# Copyright (c) 2019 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +nobase_include_HEADERS = ngtcp2/ngtcp2_crypto.h + +if HAVE_OPENSSL +nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_openssl.h +endif + +if HAVE_GNUTLS +nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_gnutls.h +endif + +if HAVE_BORINGSSL +nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_boringssl.h +endif + +if HAVE_PICOTLS +nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_picotls.h +endif + +if HAVE_WOLFSSL +nobase_include_HEADERS += ngtcp2/ngtcp2_crypto_wolfssl.h +endif diff --git a/crypto/includes/ngtcp2/ngtcp2_crypto.h b/crypto/includes/ngtcp2/ngtcp2_crypto.h new file mode 100644 index 0000000..4736b51 --- /dev/null +++ b/crypto/includes/ngtcp2/ngtcp2_crypto.h @@ -0,0 +1,893 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CRYPTO_H +#define NGTCP2_CRYPTO_H + +#include <ngtcp2/ngtcp2.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include <ws2tcpip.h> +#endif /* WIN32 */ + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_INITIAL_SECRETLEN` is the length of secret + * for Initial packets. + */ +#define NGTCP2_CRYPTO_INITIAL_SECRETLEN 32 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_INITIAL_KEYLEN` is the length of key for + * Initial packets. + */ +#define NGTCP2_CRYPTO_INITIAL_KEYLEN 16 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_INITIAL_IVLEN` is the length of IV for + * Initial packets. + */ +#define NGTCP2_CRYPTO_INITIAL_IVLEN 12 + +/** + * @function + * + * `ngtcp2_crypto_ctx_tls` initializes |ctx| by extracting negotiated + * ciphers and message digests from native TLS session + * |tls_native_handle|. This is used for encrypting/decrypting + * Handshake and Short header packets. + * + * If libngtcp2_crypto_openssl is linked, |tls_native_handle| must be + * a pointer to SSL object. + */ +NGTCP2_EXTERN ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle); + +/** + * @function + * + * `ngtcp2_crypto_ctx_tls_early` initializes |ctx| by extracting early + * ciphers and message digests from native TLS session + * |tls_native_handle|. This is used for encrypting/decrypting 0RTT + * packets. + * + * If libngtcp2_crypto_openssl is linked, |tls_native_handle| must be + * a pointer to SSL object. + */ +NGTCP2_EXTERN ngtcp2_crypto_ctx * +ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx, void *tls_native_handle); + +/** + * @function + * + * `ngtcp2_crypto_md_init` initializes |md| with the provided + * |md_native_handle| which is an underlying message digest object. + * + * If libngtcp2_crypto_openssl is linked, |md_native_handle| must be a + * pointer to EVP_MD. + * + * If libngtcp2_crypto_gnutls is linked, |md_native_handle| must be + * gnutls_mac_algorithm_t casted to ``void *``. + * + * If libngtcp2_crypto_boringssl is linked, |md_native_handle| must be + * a pointer to EVP_MD. + */ +NGTCP2_EXTERN ngtcp2_crypto_md *ngtcp2_crypto_md_init(ngtcp2_crypto_md *md, + void *md_native_handle); + +/** + * @function + * + * `ngtcp2_crypto_md_hashlen` returns the length of |md| output. + */ +NGTCP2_EXTERN size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md); + +/** + * @function + * + * `ngtcp2_crypto_aead_keylen` returns the length of key for |aead|. + */ +NGTCP2_EXTERN size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead); + +/** + * @function + * + * `ngtcp2_crypto_aead_noncelen` returns the length of nonce for + * |aead|. + */ +NGTCP2_EXTERN size_t +ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead); + +/** + * @function + * + * `ngtcp2_crypto_hkdf_extract` performs HKDF extract operation. The + * result is the length of |md| and is stored to the buffer pointed by + * |dest|. The caller is responsible to specify the buffer that can + * store the output. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen); + +/** + * @function + * + * `ngtcp2_crypto_hkdf_expand` performs HKDF expand operation. The + * result is |destlen| bytes long and is stored to the buffer pointed + * by |dest|. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, + const uint8_t *secret, + size_t secretlen, + const uint8_t *info, + size_t infolen); + +/** + * @function + * + * `ngtcp2_crypto_hkdf` performs HKDF operation. The result is + * |destlen| bytes long and is stored to the buffer pointed by |dest|. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen, + const uint8_t *info, size_t infolen); + +/** + * @function + * + * `ngtcp2_crypto_hkdf_expand_label` performs HKDF expand label. The + * result is |destlen| bytes long and is stored to the buffer pointed + * by |dest|. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_hkdf_expand_label(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, + const uint8_t *secret, + size_t secretlen, + const uint8_t *label, + size_t labellen); + +/** + * @enum + * + * :type:`ngtcp2_crypto_side` indicates which side the application + * implements; client or server. + */ +typedef enum ngtcp2_crypto_side { + /** + * :enum:`NGTCP2_CRYPTO_SIDE_CLIENT` indicates that the application + * is client. + */ + NGTCP2_CRYPTO_SIDE_CLIENT, + /** + * :enum:`NGTCP2_CRYPTO_SIDE_SERVER` indicates that the application + * is server. + */ + NGTCP2_CRYPTO_SIDE_SERVER +} ngtcp2_crypto_side; + +/** + * @function + * + * `ngtcp2_crypto_packet_protection_ivlen` returns the length of IV + * used to encrypt QUIC packet. + */ +NGTCP2_EXTERN size_t +ngtcp2_crypto_packet_protection_ivlen(const ngtcp2_crypto_aead *aead); + +/** + * @function + * + * `ngtcp2_crypto_encrypt` encrypts |plaintext| of length + * |plaintextlen| and writes the ciphertext into the buffer pointed by + * |dest|. The length of ciphertext is plaintextlen + + * :member:`aead->max_overhead <ngtcp2_crypto_aead.max_overhead>` + * bytes long. |dest| must have enough capacity to store the + * ciphertext. It is allowed to specify the same value to |dest| and + * |plaintext|. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_encrypt(uint8_t *dest, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, + size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @function + * + * `ngtcp2_crypto_encrypt_cb` is a wrapper function around + * `ngtcp2_crypto_encrypt`. It can be directly passed to + * :member:`ngtcp2_callbacks.encrypt` field. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @function + * + * `ngtcp2_crypto_decrypt` decrypts |ciphertext| of length + * |ciphertextlen| and writes the plaintext into the buffer pointed by + * |dest|. The length of plaintext is ciphertextlen - + * :member:`aead->max_overhead <ngtcp2_crypto_aead.max_overhead>` + * bytes long. |dest| must have enough capacity to store the + * plaintext. It is allowed to specify the same value to |dest| and + * |ciphertext|. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_decrypt(uint8_t *dest, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, + size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @function + * + * `ngtcp2_crypto_decrypt_cb` is a wrapper function around + * `ngtcp2_crypto_decrypt`. It can be directly passed to + * :member:`ngtcp2_callbacks.decrypt` field. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_TLS_DECRYPT`. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_decrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @function + * + * `ngtcp2_crypto_hp_mask` generates mask which is used in packet + * header encryption. The mask is written to the buffer pointed by + * |dest|. The sample is passed as |sample| which is + * :macro:`NGTCP2_HP_SAMPLELEN` bytes long. The length of mask must + * be at least :macro:`NGTCP2_HP_MASKLEN`. The library only uses the + * first :macro:`NGTCP2_HP_MASKLEN` bytes of the produced mask. The + * buffer pointed by |dest| must have at least + * :macro:`NGTCP2_HP_SAMPLELEN` bytes available. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_hp_mask(uint8_t *dest, + const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample); + +/** + * @function + * + * `ngtcp2_crypto_hp_mask_cb` is a wrapper function around + * `ngtcp2_crypto_hp_mask`. It can be directly passed to + * :member:`ngtcp2_callbacks.hp_mask` field. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample); + +/** + * @function + * + * `ngtcp2_crypto_derive_and_install_rx_key` derives the rx keys from + * |secret| and installs new keys to |conn|. + * + * If |key| is not NULL, the derived packet protection key for + * decryption is written to the buffer pointed by |key|. If |iv| is + * not NULL, the derived packet protection IV for decryption is + * written to the buffer pointed by |iv|. If |hp| is not NULL, the + * derived header protection key for decryption is written to the + * buffer pointed by |hp|. + * + * |secretlen| specifies the length of |secret|. + * + * The length of packet protection key and header protection key is + * `ngtcp2_crypto_aead_keylen(ctx->aead) <ngtcp2_crypto_aead_keylen>`, + * and the length of packet protection IV is + * `ngtcp2_crypto_packet_protection_ivlen(ctx->aead) + * <ngtcp2_crypto_packet_protection_ivlen>` where ctx is obtained by + * `ngtcp2_crypto_ctx_tls` (or `ngtcp2_crypto_ctx_tls_early` if + * |level| == :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_EARLY`). + * + * In the first call of this function, it calls + * `ngtcp2_conn_set_crypto_ctx` (or `ngtcp2_conn_set_early_crypto_ctx` + * if |level| == + * :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_EARLY`) to set + * negotiated AEAD and message digest algorithm. After the successful + * call of this function, application can use + * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_early_crypto_ctx` + * if |level| == + * :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_EARLY`) to get + * :type:`ngtcp2_crypto_ctx`. + * + * If |conn| is initialized as client, and |level| is + * :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_APPLICATION`, this + * function retrieves a remote QUIC transport parameters extension + * from an object obtained by `ngtcp2_conn_get_tls_native_handle` and + * sets it to |conn| by calling + * `ngtcp2_conn_decode_remote_transport_params`. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_derive_and_install_rx_key( + ngtcp2_conn *conn, uint8_t *key, uint8_t *iv, uint8_t *hp, + ngtcp2_crypto_level level, const uint8_t *secret, size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_derive_and_install_tx_key` derives the tx keys from + * |secret| and installs new keys to |conn|. + * + * If |key| is not NULL, the derived packet protection key for + * encryption is written to the buffer pointed by |key|. If |iv| is + * not NULL, the derived packet protection IV for encryption is + * written to the buffer pointed by |iv|. If |hp| is not NULL, the + * derived header protection key for encryption is written to the + * buffer pointed by |hp|. + * + * |secretlen| specifies the length of |secret|. + * + * The length of packet protection key and header protection key is + * `ngtcp2_crypto_aead_keylen(ctx->aead) <ngtcp2_crypto_aead_keylen>`, + * and the length of packet protection IV is + * `ngtcp2_crypto_packet_protection_ivlen(ctx->aead) + * <ngtcp2_crypto_packet_protection_ivlen>` where ctx is obtained by + * `ngtcp2_crypto_ctx_tls` (or `ngtcp2_crypto_ctx_tls_early` if + * |level| == :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_EARLY`). + * + * In the first call of this function, it calls + * `ngtcp2_conn_set_crypto_ctx` (or `ngtcp2_conn_set_early_crypto_ctx` + * if |level| == + * :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_EARLY`) to set + * negotiated AEAD and message digest algorithm. After the successful + * call of this function, application can use + * `ngtcp2_conn_get_crypto_ctx` (or `ngtcp2_conn_get_early_crypto_ctx` + * if |level| == + * :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_EARLY`) to get + * :type:`ngtcp2_crypto_ctx`. + * + * If |conn| is initialized as server, and |level| is + * :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_APPLICATION`, this + * function retrieves a remote QUIC transport parameters extension + * from an object obtained by `ngtcp2_conn_get_tls_native_handle` and + * sets it to |conn| by calling + * `ngtcp2_conn_decode_remote_transport_params`. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_derive_and_install_tx_key( + ngtcp2_conn *conn, uint8_t *key, uint8_t *iv, uint8_t *hp, + ngtcp2_crypto_level level, const uint8_t *secret, size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_update_key` updates traffic keying materials. + * + * The new traffic secret for decryption is written to the buffer + * pointed by |rx_secret|. The length of secret is |secretlen| bytes, + * and |rx_secret| must point to the buffer which has enough capacity. + * + * The new traffic secret for encryption is written to the buffer + * pointed by |tx_secret|. The length of secret is |secretlen| bytes, + * and |tx_secret| must point to the buffer which has enough capacity. + * + * The derived packet protection key for decryption is written to the + * buffer pointed by |rx_key|. The derived packet protection IV for + * decryption is written to the buffer pointed by |rx_iv|. + * |rx_aead_ctx| must be constructed with |rx_key|. + * + * The derived packet protection key for encryption is written to the + * buffer pointed by |tx_key|. The derived packet protection IV for + * encryption is written to the buffer pointed by |tx_iv|. + * |tx_aead_ctx| must be constructed with |rx_key|. + * + * |current_rx_secret| and |current_tx_secret| are the current traffic + * secrets for decryption and encryption. |secretlen| specifies the + * length of |rx_secret| and |tx_secret|. + * + * The length of packet protection key and header protection key is + * `ngtcp2_crypto_aead_keylen(ctx->aead) <ngtcp2_crypto_aead_keylen>`, + * and the length of packet protection IV is + * `ngtcp2_crypto_packet_protection_ivlen(ctx->aead) + * <ngtcp2_crypto_packet_protection_ivlen>` where ctx is obtained by + * `ngtcp2_crypto_ctx_tls`. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_update_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_key, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_key, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_update_key_cb` is a wrapper function around + * `ngtcp2_crypto_update_key`. It can be directly passed to + * :member:`ngtcp2_callbacks.update_key` field. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int ngtcp2_crypto_update_key_cb( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen, void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_client_initial_cb` installs initial secrets and + * encryption keys and sets QUIC transport parameters. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.client_initial` field. It is only used + * by client. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int ngtcp2_crypto_client_initial_cb(ngtcp2_conn *conn, + void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_recv_retry_cb` re-installs initial secrets in + * response to incoming Retry packet. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.recv_retry` field. It is only used + * by client. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int ngtcp2_crypto_recv_retry_cb(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_recv_client_initial_cb` installs initial secrets in + * response to an incoming Initial packet from client, and sets QUIC + * transport parameters. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.recv_client_initial` field. It is + * only used by server. + * + * This function returns 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + */ +NGTCP2_EXTERN int ngtcp2_crypto_recv_client_initial_cb(ngtcp2_conn *conn, + const ngtcp2_cid *dcid, + void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_read_write_crypto_data` reads CRYPTO data |data| of + * length |datalen| in encryption level |crypto_level| and may feed + * outgoing CRYPTO data to |conn|. This function can drive handshake. + * This function can be also used after handshake completes. It is + * allowed to call this function with |datalen| == 0. In this case, + * no additional read operation is done. + * + * This function returns 0 if it succeeds, or a negative error code. + * The generic error code is -1 if a specific error code is not + * suitable. The error codes less than -10000 are specific to + * underlying TLS implementation. For OpenSSL, the error codes are + * defined in *ngtcp2_crypto_openssl.h*. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen); + +/** + * @function + * + * `ngtcp2_crypto_recv_crypto_data_cb` is a wrapper function around + * `ngtcp2_crypto_read_write_crypto_data`. It can be directly passed + * to :member:`ngtcp2_callbacks.recv_crypto_data` field. + * + * If this function is used, the TLS implementation specific error + * codes described in `ngtcp2_crypto_read_write_crypto_data` are + * treated as if it returns -1. Do not use this function if an + * application wishes to use the TLS implementation specific error + * codes. + */ +NGTCP2_EXTERN int ngtcp2_crypto_recv_crypto_data_cb( + ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, uint64_t offset, + const uint8_t *data, size_t datalen, void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_generate_stateless_reset_token` generates a + * stateless reset token using HKDF extraction using the given |cid| + * and static key |secret| as input. The token will be written to + * the buffer pointed by |token| and it must have a capacity of at + * least :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` bytes. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_generate_stateless_reset_token( + uint8_t *token, const uint8_t *secret, size_t secretlen, + const ngtcp2_cid *cid); + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_TOKEN_RAND_DATALEN` is the length of random + * data added to a token generated by + * `ngtcp2_crypto_generate_retry_token` or + * `ngtcp2_crypto_generate_regular_token`. + */ +#define NGTCP2_CRYPTO_TOKEN_RAND_DATALEN 32 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY` is the magic byte for + * Retry token generated by `ngtcp2_crypto_generate_retry_token`. + */ +#define NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY 0xb6 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR` is the magic byte for a + * token generated by `ngtcp2_crypto_generate_regular_token`. + */ +#define NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR 0x36 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN` is the maximum length of + * a token generated by `ngtcp2_crypto_generate_retry_token`. + */ +#define NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN \ + (/* magic = */ 1 + /* cid len = */ 1 + NGTCP2_MAX_CIDLEN + \ + sizeof(ngtcp2_tstamp) + /* aead tag = */ 16 + \ + NGTCP2_CRYPTO_TOKEN_RAND_DATALEN) + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN` is the maximum length + * of a token generated by `ngtcp2_crypto_generate_regular_token`. + */ +#define NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN \ + (/* magic = */ 1 + sizeof(ngtcp2_tstamp) + /* aead tag = */ 16 + \ + NGTCP2_CRYPTO_TOKEN_RAND_DATALEN) + +/** + * @function + * + * `ngtcp2_crypto_generate_retry_token` generates a token in the + * buffer pointed by |token| that is sent with Retry packet. The + * buffer pointed by |token| must have at least + * :macro:`NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN` bytes long. The + * successfully generated token starts with + * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY`. |secret| of length + * |secretlen| is an initial keying material to generate keys to + * encrypt the token. |version| is QUIC version. |remote_addr| of + * length |remote_addrlen| is an address of client. |retry_scid| is a + * Source Connection ID chosen by server and set in Retry packet. + * |odcid| is a Destination Connection ID in Initial packet sent by + * client. |ts| is the timestamp when the token is generated. + * + * This function returns the length of generated token if it succeeds, + * or -1. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_generate_retry_token( + uint8_t *token, const uint8_t *secret, size_t secretlen, uint32_t version, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + const ngtcp2_cid *retry_scid, const ngtcp2_cid *odcid, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_crypto_verify_retry_token` verifies Retry token stored in + * the buffer pointed by |token| of length |tokenlen|. |secret| of + * length |secretlen| is an initial keying material to generate keys + * to decrypt the token. |version| is QUIC version of the Initial + * packet that contains this token. |remote_addr| of length + * |remote_addrlen| is an address of client. |dcid| is a Destination + * Connection ID in Initial packet sent by client. |timeout| is the + * period during which the token is valid. |ts| is the current + * timestamp. When validation succeeds, the extracted Destination + * Connection ID (which is the Destination Connection ID in Initial + * packet sent by client that triggered Retry packet) is stored to the + * buffer pointed by |odcid|. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_verify_retry_token( + ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen, + const uint8_t *secret, size_t secretlen, uint32_t version, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + const ngtcp2_cid *dcid, ngtcp2_duration timeout, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_crypto_generate_regular_token` generates a token in the + * buffer pointed by |token| that is sent with NEW_TOKEN frame. The + * buffer pointed by |token| must have at least + * :macro:`NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN` bytes long. The + * successfully generated token starts with + * :macro:`NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR`. |secret| of length + * |secretlen| is an initial keying material to generate keys to + * encrypt the token. |remote_addr| of length |remote_addrlen| is an + * address of client. |ts| is the timestamp when the token is + * generated. + * + * This function returns the length of generated token if it succeeds, + * or -1. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_generate_regular_token( + uint8_t *token, const uint8_t *secret, size_t secretlen, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_crypto_verify_regular_token` verifies a regular token + * stored in the buffer pointed by |token| of length |tokenlen|. + * |secret| of length |secretlen| is an initial keying material to + * generate keys to decrypt the token. |remote_addr| of length + * |remote_addrlen| is an address of client. |timeout| is the period + * during which the token is valid. |ts| is the current timestamp. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_verify_regular_token( + const uint8_t *token, size_t tokenlen, const uint8_t *secret, + size_t secretlen, const ngtcp2_sockaddr *remote_addr, + ngtcp2_socklen remote_addrlen, ngtcp2_duration timeout, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_crypto_write_connection_close` writes Initial packet + * containing CONNECTION_CLOSE with the given |error_code| and the + * optional |reason| of length |reasonlen| to the buffer pointed by + * |dest| of length |destlen|. This function is designed for server + * to close connection without committing the state when validating + * Retry token fails. This function must not be used by client. The + * |dcid| must be the Source Connection ID in Initial packet from + * client. The |scid| must be the Destination Connection ID in + * Initial packet from client. |scid| is used to derive initial + * keying materials. + * + * This function wraps around `ngtcp2_pkt_write_connection_close` for + * easier use. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_write_connection_close( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, uint64_t error_code, const uint8_t *reason, + size_t reasonlen); + +/** + * @function + * + * `ngtcp2_crypto_write_retry` writes Retry packet to the buffer + * pointed by |dest| of length |destlen|. |odcid| specifies Original + * Destination Connection ID. |token| specifies Retry Token, and + * |tokenlen| specifies its length. + * + * This function wraps around `ngtcp2_pkt_write_retry` for easier use. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_crypto_write_retry( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token, + size_t tokenlen); + +/** + * @function + * + * `ngtcp2_crypto_aead_ctx_encrypt_init` initializes |aead_ctx| with + * new AEAD cipher context object for encryption which is constructed + * to use |key| as encryption key. |aead| specifies AEAD cipher to + * use. |noncelen| is the length of nonce. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen); + +/** + * @function + * + * `ngtcp2_crypto_aead_ctx_decrypt_init` initializes |aead_ctx| with + * new AEAD cipher context object for decryption which is constructed + * to use |key| as encryption key. |aead| specifies AEAD cipher to + * use. |noncelen| is the length of nonce. + * + * This function returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen); + +/** + * @function + * + * `ngtcp2_crypto_aead_ctx_free` frees up resources used by + * |aead_ctx|. This function does not free the memory pointed by + * |aead_ctx| itself. + */ +NGTCP2_EXTERN void +ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx); + +/** + * @function + * + * `ngtcp2_crypto_delete_crypto_aead_ctx_cb` deletes the given |aead_ctx|. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.delete_crypto_aead_ctx` field. + */ +NGTCP2_EXTERN void ngtcp2_crypto_delete_crypto_aead_ctx_cb( + ngtcp2_conn *conn, ngtcp2_crypto_aead_ctx *aead_ctx, void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_delete_crypto_cipher_ctx_cb` deletes the given + * |cipher_ctx|. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.delete_crypto_cipher_ctx` field. + */ +NGTCP2_EXTERN void ngtcp2_crypto_delete_crypto_cipher_ctx_cb( + ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_get_path_challenge_data_cb` writes unpredictable + * sequence of :macro:`NGTCP2_PATH_CHALLENGE_DATALEN` bytes to |data| + * which is sent with PATH_CHALLENGE frame. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.get_path_challenge_data` field. + */ +NGTCP2_EXTERN int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, + uint8_t *data, + void *user_data); + +/** + * @function + * + * `ngtcp2_crypto_version_negotiation_cb` installs Initial keys for + * |version| which is negotiated or being negotiated. |client_dcid| + * is the destination connection ID in first Initial packet of client. + * + * This function can be directly passed to + * :member:`ngtcp2_callbacks.version_negotiation` field. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_version_negotiation_cb(ngtcp2_conn *conn, uint32_t version, + const ngtcp2_cid *client_dcid, + void *user_data); + +typedef struct ngtcp2_crypto_conn_ref ngtcp2_crypto_conn_ref; + +/** + * @functypedef + * + * :type:`ngtcp2_crypto_get_conn` is a callback function to get a + * pointer to :type:`ngtcp2_conn` from |conn_ref|. The implementation + * must return non-NULL :type:`ngtcp2_conn` object. + */ +typedef ngtcp2_conn *(*ngtcp2_crypto_get_conn)( + ngtcp2_crypto_conn_ref *conn_ref); + +/** + * @struct + * + * :type:`ngtcp2_crypto_conn_ref` is a structure to get a pointer to + * :type:`ngtcp2_conn`. It is meant to be set to TLS native handle as + * an application specific data (e.g. SSL_set_app_data in OpenSSL). + */ +typedef struct ngtcp2_crypto_conn_ref { + /** + * :member:`get_conn` is a callback function to get a pointer to + * :type:`ngtcp2_conn` object. + */ + ngtcp2_crypto_get_conn get_conn; + /** + * :member:`user_data` is a pointer to arbitrary user data. + */ + void *user_data; +} ngtcp2_crypto_conn_ref; + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_CRYPTO_H */ diff --git a/crypto/includes/ngtcp2/ngtcp2_crypto_boringssl.h b/crypto/includes/ngtcp2/ngtcp2_crypto_boringssl.h new file mode 100644 index 0000000..6497c09 --- /dev/null +++ b/crypto/includes/ngtcp2/ngtcp2_crypto_boringssl.h @@ -0,0 +1,104 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CRYPTO_BORINGSSL_H +#define NGTCP2_CRYPTO_BORINGSSL_H + +#include <ngtcp2/ngtcp2.h> + +#include <openssl/ssl.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @function + * + * `ngtcp2_crypto_boringssl_from_ssl_encryption_level` translates + * |ssl_level| to :type:`ngtcp2_crypto_level`. This function is only + * available for BoringSSL backend. + */ +NGTCP2_EXTERN ngtcp2_crypto_level +ngtcp2_crypto_boringssl_from_ssl_encryption_level( + enum ssl_encryption_level_t ssl_level); + +/** + * @function + * + * `ngtcp2_crypto_boringssl_from_ngtcp2_crypto_level` translates + * |crypto_level| to ssl_encryption_level_t. This function is only + * available for BoringSSL backend. + */ +NGTCP2_EXTERN enum ssl_encryption_level_t +ngtcp2_crypto_boringssl_from_ngtcp2_crypto_level( + ngtcp2_crypto_level crypto_level); + +/** + * @function + * + * `ngtcp2_crypto_boringssl_configure_server_context` configures + * |ssl_ctx| for server side QUIC connection. It performs the + * following modifications: + * + * - Set minimum and maximum TLS version to TLSv1.3. + * - Set SSL_QUIC_METHOD by calling SSL_CTX_set_quic_method. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * SSL object by calling SSL_set_app_data, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_boringssl_configure_server_context(SSL_CTX *ssl_ctx); + +/** + * @function + * + * `ngtcp2_crypto_boringssl_configure_client_context` configures + * |ssl_ctx| for client side QUIC connection. It performs the + * following modifications: + * + * - Set minimum and maximum TLS version to TLSv1.3. + * - Set SSL_QUIC_METHOD by calling SSL_CTX_set_quic_method. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * SSL object by calling SSL_set_app_data, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_boringssl_configure_client_context(SSL_CTX *ssl_ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_CRYPTO_BORINGSSL_H */ diff --git a/crypto/includes/ngtcp2/ngtcp2_crypto_gnutls.h b/crypto/includes/ngtcp2/ngtcp2_crypto_gnutls.h new file mode 100644 index 0000000..af5503f --- /dev/null +++ b/crypto/includes/ngtcp2/ngtcp2_crypto_gnutls.h @@ -0,0 +1,107 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CRYPTO_GNUTLS_H +#define NGTCP2_CRYPTO_GNUTLS_H + +#include <ngtcp2/ngtcp2.h> + +#include <gnutls/gnutls.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @function + * + * `ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level` + * translates |gtls_level| to :type:`ngtcp2_crypto_level`. This + * function is only available for GnuTLS backend. + */ +NGTCP2_EXTERN ngtcp2_crypto_level +ngtcp2_crypto_gnutls_from_gnutls_record_encryption_level( + gnutls_record_encryption_level_t gtls_level); + +/** + * @function + * + * `ngtcp2_crypto_gnutls_from_ngtcp2_crypto_level` translates + * |crypto_level| to gnutls_record_encryption_level_t. This function + * is only available for GnuTLS backend. + */ +NGTCP2_EXTERN gnutls_record_encryption_level_t +ngtcp2_crypto_gnutls_from_ngtcp2_level(ngtcp2_crypto_level crypto_level); + +/** + * @function + * + * `ngtcp2_crypto_gnutls_configure_server_session` configures + * |session| for server side QUIC connection. It performs the + * following modifications: + * + * - Set gnutls_handshake_set_secret_function. + * - Set gnutls_handshake_set_read_function. + * - Set gnutls_alert_set_read_function. + * - Register a TLS extension handler for QUIC Transport Parameters. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * gnutls_session_t object by calling gnutls_session_set_ptr, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_gnutls_configure_server_session(gnutls_session_t session); + +/** + * @function + * + * `ngtcp2_crypto_gnutls_configure_client_session` configures + * |session| for client side QUIC connection. It performs the + * following modifications: + * + * - Set gnutls_handshake_set_secret_function. + * - Set gnutls_handshake_set_read_function. + * - Set gnutls_alert_set_read_function. + * - Register a TLS extension handler for QUIC Transport Parameters. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * gnutls_session_t object by calling gnutls_session_set_ptr, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_gnutls_configure_client_session(gnutls_session_t session); + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_CRYPTO_GNUTLS_H */ diff --git a/crypto/includes/ngtcp2/ngtcp2_crypto_openssl.h b/crypto/includes/ngtcp2/ngtcp2_crypto_openssl.h new file mode 100644 index 0000000..844081b --- /dev/null +++ b/crypto/includes/ngtcp2/ngtcp2_crypto_openssl.h @@ -0,0 +1,132 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CRYPTO_OPENSSL_H +#define NGTCP2_CRYPTO_OPENSSL_H + +#include <ngtcp2/ngtcp2.h> + +#include <openssl/ssl.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @macrosection + * + * OpenSSL specific error codes + */ + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_X509_LOOKUP` is the + * error code which indicates that TLS handshake routine is + * interrupted by X509 certificate lookup. See + * :macro:`SSL_ERROR_WANT_X509_LOOKUP` error description from + * `SSL_do_handshake`. + */ +#define NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_X509_LOOKUP -10001 + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_CLIENT_HELLO_CB` is the + * error code which indicates that TLS handshake routine is + * interrupted by client hello callback. See + * :macro:`SSL_ERROR_WANT_CLIENT_HELLO_CB` error description from + * `SSL_do_handshake`. + */ +#define NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_CLIENT_HELLO_CB -10002 + +/** + * @function + * + * `ngtcp2_crypto_openssl_from_ossl_encryption_level` translates + * |ossl_level| to :type:`ngtcp2_crypto_level`. This function is only + * available for OpenSSL backend. + */ +NGTCP2_EXTERN ngtcp2_crypto_level +ngtcp2_crypto_openssl_from_ossl_encryption_level( + OSSL_ENCRYPTION_LEVEL ossl_level); + +/** + * @function + * + * `ngtcp2_crypto_openssl_from_ngtcp2_crypto_level` translates + * |crypto_level| to OSSL_ENCRYPTION_LEVEL. This function is only + * available for OpenSSL backend. + */ +NGTCP2_EXTERN OSSL_ENCRYPTION_LEVEL +ngtcp2_crypto_openssl_from_ngtcp2_crypto_level( + ngtcp2_crypto_level crypto_level); + +/** + * @function + * + * `ngtcp2_crypto_openssl_configure_server_context` configures + * |ssl_ctx| for server side QUIC connection. It performs the + * following modifications: + * + * - Set minimum and maximum TLS version to TLSv1.3. + * - Set SSL_QUIC_METHOD by calling SSL_CTX_set_quic_method. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * SSL object by calling SSL_set_app_data, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_openssl_configure_server_context(SSL_CTX *ssl_ctx); + +/** + * @function + * + * `ngtcp2_crypto_openssl_configure_client_context` configures + * |ssl_ctx| for client side QUIC connection. It performs the + * following modifications: + * + * - Set minimum and maximum TLS version to TLSv1.3. + * - Set SSL_QUIC_METHOD by calling SSL_CTX_set_quic_method. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * SSL object by calling SSL_set_app_data, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_openssl_configure_client_context(SSL_CTX *ssl_ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_CRYPTO_OPENSSL_H */ diff --git a/crypto/includes/ngtcp2/ngtcp2_crypto_picotls.h b/crypto/includes/ngtcp2/ngtcp2_crypto_picotls.h new file mode 100644 index 0000000..d4b551c --- /dev/null +++ b/crypto/includes/ngtcp2/ngtcp2_crypto_picotls.h @@ -0,0 +1,246 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CRYPTO_PICOTLS_H +#define NGTCP2_CRYPTO_PICOTLS_H + +#include <ngtcp2/ngtcp2.h> + +#include <picotls.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @struct + * + * :type:`ngtcp2_crypto_picotls_ctx` contains per-connection state + * of Picotls objects and must be an object to bet set to + * `ngtcp2_conn_set_tls_native_handle`. + */ +typedef struct ngtcp2_crypto_picotls_ctx { + /** + * :member:`ptls` is a pointer to ptls_t object. + */ + ptls_t *ptls; + /** + * :member:`handshake_properties` is a set of configurations used + * during this particular TLS handshake. + */ + ptls_handshake_properties_t handshake_properties; +} ngtcp2_crypto_picotls_ctx; + +/** + * @function + * + * `ngtcp2_crypto_picotls_ctx_init` initializes the object pointed by + * |cptls|. |cptls| must not be NULL. + */ +NGTCP2_EXTERN void +ngtcp2_crypto_picotls_ctx_init(ngtcp2_crypto_picotls_ctx *cptls); + +/** + * @function + * + * `ngtcp2_crypto_picotls_from_epoch` translates |epoch| to + * :type:`ngtcp2_crypto_level`. This function is only available for + * Picotls backend. + */ +NGTCP2_EXTERN ngtcp2_crypto_level +ngtcp2_crypto_picotls_from_epoch(size_t epoch); + +/** + * @function + * + * `ngtcp2_crypto_picotls_from_ngtcp2_crypto_level` translates + * |crypto_level| to epoch. This function is only available for + * Picotls backend. + */ +NGTCP2_EXTERN size_t ngtcp2_crypto_picotls_from_ngtcp2_crypto_level( + ngtcp2_crypto_level crypto_level); + +/** + * @function + * + * `ngtcp2_crypto_picotls_configure_server_context` configures |ctx| + * for server side QUIC connection. It performs the following + * modifications: + * + * - Set max_early_data_size to UINT32_MAX. + * - Set omit_end_of_early_data to 1. + * - Set update_traffic_key callback. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * ptls_t object by assigning the pointer using ptls_get_data_ptr, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_picotls_configure_server_context(ptls_context_t *ctx); + +/** + * @function + * + * `ngtcp2_crypto_picotls_configure_client_context` configures |ctx| + * for client side QUIC connection. It performs the following + * modifications: + * + * - Set omit_end_of_early_data to 1. + * - Set update_traffic_key callback. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * ptls_t object by assigning the pointer using ptls_get_data_ptr, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_picotls_configure_client_context(ptls_context_t *ctx); + +/** + * @function + * + * `ngtcp2_crypto_picotls_configure_server_session` configures |cptls| + * for server side QUIC connection. It performs the following + * modifications: + * + * - Set handshake_properties.collect_extension to + * `ngtcp2_crypto_picotls_collect_extension`. + * - Set handshake_properties.collected_extensions to + * `ngtcp2_crypto_picotls_collected_extensions`. + * + * The callbacks set by this function only handle QUIC Transport + * Parameters TLS extension. If an application needs to handle the + * other TLS extensions, set its own callbacks and call + * `ngtcp2_crypto_picotls_collect_extension` and + * `ngtcp2_crypto_picotls_collected_extensions` form them. + * + * During the QUIC handshake, the first element of + * handshake_properties.additional_extensions is assigned to send QUIC + * Transport Parameter TLS extension. Therefore, an application must + * allocate at least 2 elements for + * handshake_properties.additional_extensions. + * + * Call `ngtcp2_crypto_picotls_deconfigure_session` to free up the + * resources. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * ptls_t object by assigning the pointer using ptls_get_data_ptr, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int ngtcp2_crypto_picotls_configure_server_session( + ngtcp2_crypto_picotls_ctx *cptls); + +/** + * @function + * + * `ngtcp2_crypto_picotls_configure_client_session` configures |cptls| + * for client side QUIC connection. It performs the following + * modifications: + * + * - Set handshake_properties.max_early_data_size to a pointer to + * uint32_t, which is allocated dynamically by this function. + * - Set handshake_properties.collect_extension to + * `ngtcp2_crypto_picotls_collect_extension`. + * - Set handshake_properties.collected_extensions to + * `ngtcp2_crypto_picotls_collected_extensions`. + * - Set handshake_properties.additional_extensions[0].data to the + * dynamically allocated buffer which contains QUIC Transport + * Parameters TLS extension. An application must allocate at least + * 2 elements for handshake_properties.additional_extensions. + * + * The callbacks set by this function only handle QUIC Transport + * Parameters TLS extension. If an application needs to handle the + * other TLS extensions, set its own callbacks and call + * `ngtcp2_crypto_picotls_collect_extension` and + * `ngtcp2_crypto_picotls_collected_extensions` form them. + * + * Call `ngtcp2_crypto_picotls_deconfigure_session` to free up the + * resources. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * ptls_t object by assigning the pointer using ptls_get_data_ptr, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_picotls_configure_client_session(ngtcp2_crypto_picotls_ctx *cptls, + ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_crypto_picotls_deconfigure_session` frees the resources + * allocated for |cptls| during QUIC connection. It frees the + * following data using :manpage:`free(3)`. + * + * - handshake_properties.max_early_data_size + * - handshake_properties.additional_extensions[0].data.base + * + * If |cptls| is NULL, this function does nothing. + */ +NGTCP2_EXTERN void +ngtcp2_crypto_picotls_deconfigure_session(ngtcp2_crypto_picotls_ctx *cptls); + +/** + * @function + * + * `ngtcp2_crypto_picotls_collect_extension` is a callback function + * which only returns nonzero if |type| == + * :macro:`NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1`. + */ +NGTCP2_EXTERN int ngtcp2_crypto_picotls_collect_extension( + ptls_t *ptls, struct st_ptls_handshake_properties_t *properties, + uint16_t type); + +/** + * @function + * + * `ngtcp2_crypto_picotls_collected_extensions` is a callback function + * which only handles the extension of type + * :macro:`NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1`. The other + * extensions are ignored. + */ +NGTCP2_EXTERN int ngtcp2_crypto_picotls_collected_extensions( + ptls_t *ptls, struct st_ptls_handshake_properties_t *properties, + ptls_raw_extension_t *extensions); + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_CRYPTO_PICOTLS_H */ diff --git a/crypto/includes/ngtcp2/ngtcp2_crypto_wolfssl.h b/crypto/includes/ngtcp2/ngtcp2_crypto_wolfssl.h new file mode 100644 index 0000000..3b10802 --- /dev/null +++ b/crypto/includes/ngtcp2/ngtcp2_crypto_wolfssl.h @@ -0,0 +1,106 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CRYPTO_WOLFSSL_H +#define NGTCP2_CRYPTO_WOLFSSL_H + +#include <ngtcp2/ngtcp2.h> + +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> +#include <wolfssl/quic.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @function + * + * `ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level` translates + * |wolfssl_level| to :type:`ngtcp2_crypto_level`. This function is only + * available for wolfSSL backend. + */ +NGTCP2_EXTERN ngtcp2_crypto_level +ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level( + WOLFSSL_ENCRYPTION_LEVEL wolfssl_level); + +/** + * @function + * + * `ngtcp2_crypto_wolfssl_from_ngtcp2_crypto_level` translates + * |crypto_level| to WOLFSSL_ENCRYPTION_LEVEL. This function is only + * available for wolfSSL backend. + */ +NGTCP2_EXTERN WOLFSSL_ENCRYPTION_LEVEL +ngtcp2_crypto_wolfssl_from_ngtcp2_crypto_level( + ngtcp2_crypto_level crypto_level); + +/** + * @function + * + * `ngtcp2_crypto_wolfssl_configure_server_context` configures + * |ssl_ctx| for server side QUIC connection. It performs the + * following modifications: + * + * - Set minimum and maximum TLS version to TLSv1.3. + * - Set WOLFSSL_QUIC_METHOD by calling wolfSSL_CTX_set_quic_method. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * WOLFSSL object by calling wolfSSL_set_app_data, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_wolfssl_configure_server_context(WOLFSSL_CTX *ssl_ctx); + +/** + * @function + * + * `ngtcp2_crypto_wolfssl_configure_client_context` configures + * |ssl_ctx| for client side QUIC connection. It performs the + * following modifications: + * + * - Set minimum and maximum TLS version to TLSv1.3. + * - Set WOLFSSL_QUIC_METHOD by calling wolfSSL_CTX_set_quic_method. + * + * Application must set a pointer to :type:`ngtcp2_crypto_conn_ref` to + * SSL object by calling wolfSSL_set_app_data, and + * :type:`ngtcp2_crypto_conn_ref` object must have + * :member:`ngtcp2_crypto_conn_ref.get_conn` field assigned to get + * :type:`ngtcp2_conn`. + * + * It returns 0 if it succeeds, or -1. + */ +NGTCP2_EXTERN int +ngtcp2_crypto_wolfssl_configure_client_context(WOLFSSL_CTX *ssl_ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_CRYPTO_WOLFSSL_H */ diff --git a/crypto/openssl/.gitignore b/crypto/openssl/.gitignore new file mode 100644 index 0000000..296fddc --- /dev/null +++ b/crypto/openssl/.gitignore @@ -0,0 +1 @@ +/libngtcp2_crypto_openssl.pc diff --git a/crypto/openssl/CMakeLists.txt b/crypto/openssl/CMakeLists.txt new file mode 100644 index 0000000..d9a126c --- /dev/null +++ b/crypto/openssl/CMakeLists.txt @@ -0,0 +1,85 @@ +# ngtcp2 + +# Copyright (c) 2019 ngtcp2 + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +add_definitions(-DBUILDING_NGTCP2) + +set(ngtcp2_crypto_openssl_SOURCES + openssl.c + ../shared.c +) + +set(ngtcp2_crypto_openssl_INCLUDE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto" + "${OPENSSL_INCLUDE_DIRS}" +) + +foreach(name libngtcp2_crypto_openssl.pc) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +# Public shared library +if(ENABLE_SHARED_LIB) + add_library(ngtcp2_crypto_openssl SHARED ${ngtcp2_crypto_openssl_SOURCES}) + set_target_properties(ngtcp2_crypto_openssl PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${CRYPTO_OPENSSL_LT_VERSION} + SOVERSION ${CRYPTO_OPENSSL_LT_SOVERSION} + C_VISIBILITY_PRESET hidden + POSITION_INDEPENDENT_CODE ON + ) + target_include_directories(ngtcp2_crypto_openssl PUBLIC + ${ngtcp2_crypto_openssl_INCLUDE_DIRS}) + target_link_libraries(ngtcp2_crypto_openssl ngtcp2 ${OPENSSL_LIBRARIES}) + + install(TARGETS ngtcp2_crypto_openssl + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() + +if(ENABLE_STATIC_LIB) + # Public static library + add_library(ngtcp2_crypto_openssl_static ${ngtcp2_crypto_openssl_SOURCES}) + set_target_properties(ngtcp2_crypto_openssl_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${CRYPTO_OPENSSL_LT_VERSION} + SOVERSION ${CRYPTO_OPENSSL_LT_SOVERSION} + C_VISIBILITY_PRESET hidden + ) + target_compile_definitions(ngtcp2_crypto_openssl_static PUBLIC + "-DNGTCP2_STATICLIB") + target_include_directories(ngtcp2_crypto_openssl_static PUBLIC + ${ngtcp2_crypto_openssl_INCLUDE_DIRS}) + + install(TARGETS ngtcp2_crypto_openssl_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") +endif() + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_openssl.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/crypto/openssl/Makefile.am b/crypto/openssl/Makefile.am new file mode 100644 index 0000000..6880b8b --- /dev/null +++ b/crypto/openssl/Makefile.am @@ -0,0 +1,43 @@ +# ngtcp2 + +# Copyright (c) 2019 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG) +AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \ + -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \ + -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \ + @OPENSSL_CFLAGS@ +AM_LDFLAGS = ${LIBTOOL_LDFLAGS} + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libngtcp2_crypto_openssl.pc +DISTCLEANFILES = $(pkgconfig_DATA) + +lib_LTLIBRARIES = libngtcp2_crypto_openssl.la + +libngtcp2_crypto_openssl_la_SOURCES = openssl.c ../shared.c ../shared.h +libngtcp2_crypto_openssl_la_LDFLAGS = -no-undefined \ + -version-info $(CRYPTO_OPENSSL_LT_CURRENT):$(CRYPTO_OPENSSL_LT_REVISION):$(CRYPTO_OPENSSL_LT_AGE) +libngtcp2_crypto_openssl_la_LIBADD = $(top_builddir)/lib/libngtcp2.la \ + @OPENSSL_LIBS@ diff --git a/crypto/openssl/libngtcp2_crypto_openssl.pc.in b/crypto/openssl/libngtcp2_crypto_openssl.pc.in new file mode 100644 index 0000000..f3226a9 --- /dev/null +++ b/crypto/openssl/libngtcp2_crypto_openssl.pc.in @@ -0,0 +1,33 @@ +# ngtcp2 + +# Copyright (c) 2019 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libngtcp2_crypto_openssl +Description: ngtcp2 OpenSSL crypto library +URL: https://github.com/ngtcp2/ngtcp2 +Version: @VERSION@ +Libs: -L${libdir} -lngtcp2_crypto_openssl +Cflags: -I${includedir} diff --git a/crypto/openssl/openssl.c b/crypto/openssl/openssl.c new file mode 100644 index 0000000..466d9e1 --- /dev/null +++ b/crypto/openssl/openssl.c @@ -0,0 +1,807 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <assert.h> + +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_openssl.h> + +#include <openssl/ssl.h> +#include <openssl/evp.h> +#include <openssl/kdf.h> +#include <openssl/rand.h> + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +# include <openssl/core_names.h> +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ + +#include "shared.h" + +static size_t crypto_aead_max_overhead(const EVP_CIPHER *aead) { + switch (EVP_CIPHER_nid(aead)) { + case NID_aes_128_gcm: + case NID_aes_256_gcm: + return EVP_GCM_TLS_TAG_LEN; + case NID_chacha20_poly1305: + return EVP_CHACHAPOLY_TLS_TAG_LEN; + case NID_aes_128_ccm: + return EVP_CCM_TLS_TAG_LEN; + default: + assert(0); + abort(); /* if NDEBUG is set */ + } +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)EVP_aes_128_gcm()); +} + +ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + md->native_handle = (void *)EVP_sha256(); + return md; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)EVP_aes_128_gcm()); + ctx->md.native_handle = (void *)EVP_sha256(); + ctx->hp.native_handle = (void *)EVP_aes_128_ctr(); + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead, + void *aead_native_handle) { + aead->native_handle = aead_native_handle; + aead->max_overhead = crypto_aead_max_overhead(aead_native_handle); + return aead; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)EVP_aes_128_gcm()); +} + +static const EVP_CIPHER *crypto_ssl_get_aead(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_3_CK_AES_128_GCM_SHA256: + return EVP_aes_128_gcm(); + case TLS1_3_CK_AES_256_GCM_SHA384: + return EVP_aes_256_gcm(); + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return EVP_chacha20_poly1305(); + case TLS1_3_CK_AES_128_CCM_SHA256: + return EVP_aes_128_ccm(); + default: + return NULL; + } +} + +static uint64_t crypto_ssl_get_aead_max_encryption(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_AES_256_GCM_SHA384: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM; + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305; + case TLS1_3_CK_AES_128_CCM_SHA256: + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM; + default: + return 0; + } +} + +static uint64_t crypto_ssl_get_aead_max_decryption_failure(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_AES_256_GCM_SHA384: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM; + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305; + case TLS1_3_CK_AES_128_CCM_SHA256: + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM; + default: + return 0; + } +} + +static const EVP_CIPHER *crypto_ssl_get_hp(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_AES_128_CCM_SHA256: + return EVP_aes_128_ctr(); + case TLS1_3_CK_AES_256_GCM_SHA384: + return EVP_aes_256_ctr(); + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + return EVP_chacha20(); + default: + return NULL; + } +} + +static const EVP_MD *crypto_ssl_get_md(SSL *ssl) { + switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) { + case TLS1_3_CK_AES_128_GCM_SHA256: + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: + case TLS1_3_CK_AES_128_CCM_SHA256: + return EVP_sha256(); + case TLS1_3_CK_AES_256_GCM_SHA384: + return EVP_sha384(); + default: + return NULL; + } +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + SSL *ssl = tls_native_handle; + ngtcp2_crypto_aead_init(&ctx->aead, (void *)crypto_ssl_get_aead(ssl)); + ctx->md.native_handle = (void *)crypto_ssl_get_md(ssl); + ctx->hp.native_handle = (void *)crypto_ssl_get_hp(ssl); + ctx->max_encryption = crypto_ssl_get_aead_max_encryption(ssl); + ctx->max_decryption_failure = crypto_ssl_get_aead_max_decryption_failure(ssl); + return ctx; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + return ngtcp2_crypto_ctx_tls(ctx, tls_native_handle); +} + +static size_t crypto_md_hashlen(const EVP_MD *md) { + return (size_t)EVP_MD_size(md); +} + +size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) { + return crypto_md_hashlen(md->native_handle); +} + +static size_t crypto_aead_keylen(const EVP_CIPHER *aead) { + return (size_t)EVP_CIPHER_key_length(aead); +} + +size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) { + return crypto_aead_keylen(aead->native_handle); +} + +static size_t crypto_aead_noncelen(const EVP_CIPHER *aead) { + return (size_t)EVP_CIPHER_iv_length(aead); +} + +size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) { + return crypto_aead_noncelen(aead->native_handle); +} + +int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + const EVP_CIPHER *cipher = aead->native_handle; + int cipher_nid = EVP_CIPHER_nid(cipher); + EVP_CIPHER_CTX *actx; + size_t taglen = crypto_aead_max_overhead(cipher); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM params[3]; +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ + + actx = EVP_CIPHER_CTX_new(); + if (actx == NULL) { + return -1; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_IVLEN, &noncelen); + + if (cipher_nid == NID_aes_128_ccm) { + params[1] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, + NULL, taglen); + params[2] = OSSL_PARAM_construct_end(); + } else { + params[1] = OSSL_PARAM_construct_end(); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ + + if (!EVP_EncryptInit_ex(actx, cipher, NULL, NULL, NULL) || +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + !EVP_CIPHER_CTX_set_params(actx, params) || +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen, + NULL) || + (cipher_nid == NID_aes_128_ccm && + !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen, NULL)) || +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + !EVP_EncryptInit_ex(actx, NULL, NULL, key, NULL)) { + EVP_CIPHER_CTX_free(actx); + return -1; + } + + aead_ctx->native_handle = actx; + + return 0; +} + +int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + const EVP_CIPHER *cipher = aead->native_handle; + int cipher_nid = EVP_CIPHER_nid(cipher); + EVP_CIPHER_CTX *actx; + size_t taglen = crypto_aead_max_overhead(cipher); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM params[3]; +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ + + actx = EVP_CIPHER_CTX_new(); + if (actx == NULL) { + return -1; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_IVLEN, &noncelen); + + if (cipher_nid == NID_aes_128_ccm) { + params[1] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, + NULL, taglen); + params[2] = OSSL_PARAM_construct_end(); + } else { + params[1] = OSSL_PARAM_construct_end(); + } +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ + + if (!EVP_DecryptInit_ex(actx, cipher, NULL, NULL, NULL) || +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + !EVP_CIPHER_CTX_set_params(actx, params) || +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, (int)noncelen, + NULL) || + (cipher_nid == NID_aes_128_ccm && + !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen, NULL)) || +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + !EVP_DecryptInit_ex(actx, NULL, NULL, key, NULL)) { + EVP_CIPHER_CTX_free(actx); + return -1; + } + + aead_ctx->native_handle = actx; + + return 0; +} + +void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) { + if (aead_ctx->native_handle) { + EVP_CIPHER_CTX_free(aead_ctx->native_handle); + } +} + +int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, + const ngtcp2_crypto_cipher *cipher, + const uint8_t *key) { + EVP_CIPHER_CTX *actx; + + actx = EVP_CIPHER_CTX_new(); + if (actx == NULL) { + return -1; + } + + if (!EVP_EncryptInit_ex(actx, cipher->native_handle, NULL, key, NULL)) { + EVP_CIPHER_CTX_free(actx); + return -1; + } + + cipher_ctx->native_handle = actx; + + return 0; +} + +void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) { + if (cipher_ctx->native_handle) { + EVP_CIPHER_CTX_free(cipher_ctx->native_handle); + } +} + +int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + const EVP_MD *prf = md->native_handle; + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "hkdf", NULL); + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + int mode = EVP_KDF_HKDF_MODE_EXTRACT_ONLY; + OSSL_PARAM params[] = { + OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode), + OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, + (char *)EVP_MD_get0_name(prf), 0), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret, + secretlen), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt, + saltlen), + OSSL_PARAM_construct_end(), + }; + int rv = 0; + + EVP_KDF_free(kdf); + + if (EVP_KDF_derive(kctx, dest, (size_t)EVP_MD_size(prf), params) <= 0) { + rv = -1; + } + + EVP_KDF_CTX_free(kctx); + + return rv; +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + const EVP_MD *prf = md->native_handle; + int rv = 0; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + size_t destlen = (size_t)EVP_MD_size(prf); + + if (pctx == NULL) { + return -1; + } + + if (EVP_PKEY_derive_init(pctx) != 1 || + EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1 || + EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 || + EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, (int)saltlen) != 1 || + EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1 || + EVP_PKEY_derive(pctx, dest, &destlen) != 1) { + rv = -1; + } + + EVP_PKEY_CTX_free(pctx); + + return rv; +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ +} + +int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *info, + size_t infolen) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + const EVP_MD *prf = md->native_handle; + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "hkdf", NULL); + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + int mode = EVP_KDF_HKDF_MODE_EXPAND_ONLY; + OSSL_PARAM params[] = { + OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode), + OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, + (char *)EVP_MD_get0_name(prf), 0), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret, + secretlen), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, (void *)info, + infolen), + OSSL_PARAM_construct_end(), + }; + int rv = 0; + + EVP_KDF_free(kdf); + + if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) { + rv = -1; + } + + EVP_KDF_CTX_free(kctx); + + return rv; +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + const EVP_MD *prf = md->native_handle; + int rv = 0; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return -1; + } + + if (EVP_PKEY_derive_init(pctx) != 1 || + EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1 || + EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 || + EVP_PKEY_CTX_set1_hkdf_salt(pctx, (const unsigned char *)"", 0) != 1 || + EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1 || + EVP_PKEY_CTX_add1_hkdf_info(pctx, info, (int)infolen) != 1 || + EVP_PKEY_derive(pctx, dest, &destlen) != 1) { + rv = -1; + } + + EVP_PKEY_CTX_free(pctx); + + return rv; +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ +} + +int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, size_t saltlen, + const uint8_t *info, size_t infolen) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + const EVP_MD *prf = md->native_handle; + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "hkdf", NULL); + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM params[] = { + OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, + (char *)EVP_MD_get0_name(prf), 0), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret, + secretlen), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt, + saltlen), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, (void *)info, + infolen), + OSSL_PARAM_construct_end(), + }; + int rv = 0; + + EVP_KDF_free(kdf); + + if (EVP_KDF_derive(kctx, dest, destlen, params) <= 0) { + rv = -1; + } + + EVP_KDF_CTX_free(kctx); + + return rv; +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + const EVP_MD *prf = md->native_handle; + int rv = 0; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return -1; + } + + if (EVP_PKEY_derive_init(pctx) != 1 || + EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) != + 1 || + EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 || + EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, (int)saltlen) != 1 || + EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, (int)secretlen) != 1 || + EVP_PKEY_CTX_add1_hkdf_info(pctx, info, (int)infolen) != 1 || + EVP_PKEY_derive(pctx, dest, &destlen) != 1) { + rv = -1; + } + + EVP_PKEY_CTX_free(pctx); + + return rv; +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ +} + +int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + const EVP_CIPHER *cipher = aead->native_handle; + size_t taglen = crypto_aead_max_overhead(cipher); + int cipher_nid = EVP_CIPHER_nid(cipher); + EVP_CIPHER_CTX *actx = aead_ctx->native_handle; + int len; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM params[] = { + OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, + dest + plaintextlen, taglen), + OSSL_PARAM_construct_end(), + }; +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ + + (void)noncelen; + + if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, nonce) || + (cipher_nid == NID_aes_128_ccm && + !EVP_EncryptUpdate(actx, NULL, &len, NULL, (int)plaintextlen)) || + !EVP_EncryptUpdate(actx, NULL, &len, aad, (int)aadlen) || + !EVP_EncryptUpdate(actx, dest, &len, plaintext, (int)plaintextlen) || + !EVP_EncryptFinal_ex(actx, dest + len, &len) || +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + !EVP_CIPHER_CTX_get_params(actx, params) +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_GET_TAG, (int)taglen, + dest + plaintextlen) +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + ) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + const EVP_CIPHER *cipher = aead->native_handle; + size_t taglen = crypto_aead_max_overhead(cipher); + int cipher_nid = EVP_CIPHER_nid(cipher); + EVP_CIPHER_CTX *actx = aead_ctx->native_handle; + int len; + const uint8_t *tag; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM params[2]; +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ + + (void)noncelen; + + if (taglen > ciphertextlen) { + return -1; + } + + ciphertextlen -= taglen; + tag = ciphertext + ciphertextlen; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + params[0] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, + (void *)tag, taglen); + params[1] = OSSL_PARAM_construct_end(); +#endif /* OPENSSL_VERSION_NUMBER >= 0x30000000L */ + + if (!EVP_DecryptInit_ex(actx, NULL, NULL, NULL, nonce) || +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + !EVP_CIPHER_CTX_set_params(actx, params) || +#else /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + !EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, (int)taglen, + (uint8_t *)tag) || +#endif /* !(OPENSSL_VERSION_NUMBER >= 0x30000000L) */ + (cipher_nid == NID_aes_128_ccm && + !EVP_DecryptUpdate(actx, NULL, &len, NULL, (int)ciphertextlen)) || + !EVP_DecryptUpdate(actx, NULL, &len, aad, (int)aadlen) || + !EVP_DecryptUpdate(actx, dest, &len, ciphertext, (int)ciphertextlen) || + (cipher_nid != NID_aes_128_ccm && + !EVP_DecryptFinal_ex(actx, dest + ciphertextlen, &len))) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; + EVP_CIPHER_CTX *actx = hp_ctx->native_handle; + int len; + + (void)hp; + + if (!EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) || + !EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, sizeof(PLAINTEXT) - 1) || + !EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT) - 1, &len)) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen) { + SSL *ssl = ngtcp2_conn_get_tls_native_handle(conn); + int rv; + int err; + + if (SSL_provide_quic_data( + ssl, ngtcp2_crypto_openssl_from_ngtcp2_crypto_level(crypto_level), + data, datalen) != 1) { + return -1; + } + + if (!ngtcp2_conn_get_handshake_completed(conn)) { + rv = SSL_do_handshake(ssl); + if (rv <= 0) { + err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_WANT_CLIENT_HELLO_CB: + return NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_CLIENT_HELLO_CB; + case SSL_ERROR_WANT_X509_LOOKUP: + return NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_X509_LOOKUP; + case SSL_ERROR_SSL: + return -1; + default: + return -1; + } + } + + ngtcp2_conn_handshake_completed(conn); + } + + rv = SSL_process_quic_post_handshake(ssl); + if (rv != 1) { + err = SSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_SSL: + case SSL_ERROR_ZERO_RETURN: + return -1; + default: + return -1; + } + } + + return 0; +} + +int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) { + SSL *ssl = tls; + const uint8_t *tp; + size_t tplen; + int rv; + + SSL_get_peer_quic_transport_params(ssl, &tp, &tplen); + + rv = ngtcp2_conn_decode_remote_transport_params(conn, tp, tplen); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return -1; + } + + return 0; +} + +int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf, + size_t len) { + if (SSL_set_quic_transport_params(tls, buf, len) != 1) { + return -1; + } + + return 0; +} + +ngtcp2_crypto_level ngtcp2_crypto_openssl_from_ossl_encryption_level( + OSSL_ENCRYPTION_LEVEL ossl_level) { + switch (ossl_level) { + case ssl_encryption_initial: + return NGTCP2_CRYPTO_LEVEL_INITIAL; + case ssl_encryption_early_data: + return NGTCP2_CRYPTO_LEVEL_EARLY; + case ssl_encryption_handshake: + return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + case ssl_encryption_application: + return NGTCP2_CRYPTO_LEVEL_APPLICATION; + default: + assert(0); + abort(); /* if NDEBUG is set */ + } +} + +OSSL_ENCRYPTION_LEVEL +ngtcp2_crypto_openssl_from_ngtcp2_crypto_level( + ngtcp2_crypto_level crypto_level) { + switch (crypto_level) { + case NGTCP2_CRYPTO_LEVEL_INITIAL: + return ssl_encryption_initial; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + return ssl_encryption_handshake; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + return ssl_encryption_application; + case NGTCP2_CRYPTO_LEVEL_EARLY: + return ssl_encryption_early_data; + default: + assert(0); + abort(); /* if NDEBUG is set */ + } +} + +int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + void *user_data) { + (void)conn; + (void)user_data; + + if (RAND_bytes(data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + if (RAND_bytes(data, (int)datalen) != 1) { + return -1; + } + + return 0; +} + +static int set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, + const uint8_t *rx_secret, + const uint8_t *tx_secret, size_t secretlen) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = + ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level); + + if (rx_secret && + ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level, + rx_secret, secretlen) != 0) { + return 0; + } + + if (tx_secret && + ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level, + tx_secret, secretlen) != 0) { + return 0; + } + + return 1; +} + +static int add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, + const uint8_t *data, size_t datalen) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = + ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level); + int rv; + + rv = ngtcp2_conn_submit_crypto_data(conn, level, data, datalen); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return 0; + } + + return 1; +} + +static int flush_flight(SSL *ssl) { + (void)ssl; + return 1; +} + +static int send_alert(SSL *ssl, enum ssl_encryption_level_t level, + uint8_t alert) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + (void)level; + + ngtcp2_conn_set_tls_alert(conn, alert); + + return 1; +} + +static SSL_QUIC_METHOD quic_method = { + set_encryption_secrets, + add_handshake_data, + flush_flight, + send_alert, +}; + +static void crypto_openssl_configure_context(SSL_CTX *ssl_ctx) { + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + SSL_CTX_set_quic_method(ssl_ctx, &quic_method); +} + +int ngtcp2_crypto_openssl_configure_server_context(SSL_CTX *ssl_ctx) { + crypto_openssl_configure_context(ssl_ctx); + + return 0; +} + +int ngtcp2_crypto_openssl_configure_client_context(SSL_CTX *ssl_ctx) { + crypto_openssl_configure_context(ssl_ctx); + + return 0; +} diff --git a/crypto/picotls/.gitignore b/crypto/picotls/.gitignore new file mode 100644 index 0000000..3df25b1 --- /dev/null +++ b/crypto/picotls/.gitignore @@ -0,0 +1,2 @@ +/libngtcp2_crypto_picotls.pc +/libngtcp2_crypto_picotls.a diff --git a/crypto/picotls/CMakeLists.txt b/crypto/picotls/CMakeLists.txt new file mode 100644 index 0000000..4bfdede --- /dev/null +++ b/crypto/picotls/CMakeLists.txt @@ -0,0 +1,64 @@ +# ngtcp2 + +# Copyright (c) 2022 ngtcp2 + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +add_definitions(-DBUILDING_NGTCP2) + +set(ngtcp2_crypto_picotls_SOURCES + picotls.c + ../shared.c +) + +set(ngtcp2_crypto_picotls_INCLUDE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto" + "${PICOTLS_INCLUDE_DIRS}" + "${VANILLA_OPENSSL_INCLUDE_DIRS}" +) + +foreach(name libngtcp2_crypto_picotls.pc) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +if(ENABLE_STATIC_LIB) + # Public static library + add_library(ngtcp2_crypto_picotls_static ${ngtcp2_crypto_picotls_SOURCES}) + set_target_properties(ngtcp2_crypto_picotls_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + C_VISIBILITY_PRESET hidden + ) + target_compile_definitions(ngtcp2_crypto_picotls_static PUBLIC + "-DNGTCP2_STATICLIB") + target_include_directories(ngtcp2_crypto_picotls_static PUBLIC + ${ngtcp2_crypto_picotls_INCLUDE_DIRS}) + + install(TARGETS ngtcp2_crypto_picotls_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") +endif() + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_picotls.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/crypto/picotls/Makefile.am b/crypto/picotls/Makefile.am new file mode 100644 index 0000000..b2ed766 --- /dev/null +++ b/crypto/picotls/Makefile.am @@ -0,0 +1,39 @@ +# ngtcp2 + +# Copyright (c) 2022 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG) +AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \ + -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \ + -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \ + @PICOTLS_CFLAGS@ @VANILLA_OPENSSL_CFLAGS@ +AM_LDFLAGS = ${LIBTOOL_LDFLAGS} + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libngtcp2_crypto_picotls.pc +DISTCLEANFILES = $(pkgconfig_DATA) + +lib_LIBRARIES = libngtcp2_crypto_picotls.a + +libngtcp2_crypto_picotls_a_SOURCES = picotls.c ../shared.c ../shared.h diff --git a/crypto/picotls/libngtcp2_crypto_picotls.pc.in b/crypto/picotls/libngtcp2_crypto_picotls.pc.in new file mode 100644 index 0000000..1cb24f1 --- /dev/null +++ b/crypto/picotls/libngtcp2_crypto_picotls.pc.in @@ -0,0 +1,33 @@ +# ngtcp2 + +# Copyright (c) 2022 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libngtcp2_crypto_picotls +Description: ngtcp2 Picotls crypto library +URL: https://github.com/ngtcp2/ngtcp2 +Version: @VERSION@ +Libs: -L${libdir} -lngtcp2_crypto_picotls +Cflags: -I${includedir} diff --git a/crypto/picotls/picotls.c b/crypto/picotls/picotls.c new file mode 100644 index 0000000..b1bbb6c --- /dev/null +++ b/crypto/picotls/picotls.c @@ -0,0 +1,701 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <assert.h> +#include <string.h> + +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_picotls.h> + +#include <picotls.h> +#include <picotls/openssl.h> + +#include "shared.h" + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)&ptls_openssl_aes128gcm); +} + +ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + md->native_handle = (void *)&ptls_openssl_sha256; + return md; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)&ptls_openssl_aes128gcm); + ctx->md.native_handle = (void *)&ptls_openssl_sha256; + ctx->hp.native_handle = (void *)&ptls_openssl_aes128ctr; + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead, + void *aead_native_handle) { + ptls_aead_algorithm_t *alg = aead_native_handle; + + aead->native_handle = aead_native_handle; + aead->max_overhead = alg->tag_size; + return aead; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)&ptls_openssl_aes128gcm); +} + +static const ptls_aead_algorithm_t *crypto_ptls_get_aead(ptls_t *ptls) { + ptls_cipher_suite_t *cs = ptls_get_cipher(ptls); + + return cs->aead; +} + +static uint64_t crypto_ptls_get_aead_max_encryption(ptls_t *ptls) { + ptls_cipher_suite_t *cs = ptls_get_cipher(ptls); + + if (cs->aead == &ptls_openssl_aes128gcm || + cs->aead == &ptls_openssl_aes256gcm) { + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM; + } + + if (cs->aead == &ptls_openssl_chacha20poly1305) { + return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305; + } + + return 0; +} + +static uint64_t crypto_ptls_get_aead_max_decryption_failure(ptls_t *ptls) { + ptls_cipher_suite_t *cs = ptls_get_cipher(ptls); + + if (cs->aead == &ptls_openssl_aes128gcm || + cs->aead == &ptls_openssl_aes256gcm) { + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM; + } + + if (cs->aead == &ptls_openssl_chacha20poly1305) { + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305; + } + + return 0; +} + +static const ptls_cipher_algorithm_t *crypto_ptls_get_hp(ptls_t *ptls) { + ptls_cipher_suite_t *cs = ptls_get_cipher(ptls); + + if (cs->aead == &ptls_openssl_aes128gcm) { + return &ptls_openssl_aes128ctr; + } + + if (cs->aead == &ptls_openssl_aes256gcm) { + return &ptls_openssl_aes256ctr; + } + + if (cs->aead == &ptls_openssl_chacha20poly1305) { + return &ptls_openssl_chacha20; + } + + return NULL; +} + +static const ptls_hash_algorithm_t *crypto_ptls_get_md(ptls_t *ptls) { + ptls_cipher_suite_t *cs = ptls_get_cipher(ptls); + + return cs->hash; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + ngtcp2_crypto_picotls_ctx *cptls = tls_native_handle; + ngtcp2_crypto_aead_init(&ctx->aead, + (void *)crypto_ptls_get_aead(cptls->ptls)); + ctx->md.native_handle = (void *)crypto_ptls_get_md(cptls->ptls); + ctx->hp.native_handle = (void *)crypto_ptls_get_hp(cptls->ptls); + ctx->max_encryption = crypto_ptls_get_aead_max_encryption(cptls->ptls); + ctx->max_decryption_failure = + crypto_ptls_get_aead_max_decryption_failure(cptls->ptls); + return ctx; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + return ngtcp2_crypto_ctx_tls(ctx, tls_native_handle); +} + +static size_t crypto_md_hashlen(const ptls_hash_algorithm_t *md) { + return md->digest_size; +} + +size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) { + return crypto_md_hashlen(md->native_handle); +} + +static size_t crypto_aead_keylen(const ptls_aead_algorithm_t *aead) { + return aead->key_size; +} + +size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) { + return crypto_aead_keylen(aead->native_handle); +} + +static size_t crypto_aead_noncelen(const ptls_aead_algorithm_t *aead) { + return aead->iv_size; +} + +size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) { + return crypto_aead_noncelen(aead->native_handle); +} + +int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + const ptls_aead_algorithm_t *cipher = aead->native_handle; + size_t keylen = crypto_aead_keylen(cipher); + ptls_aead_context_t *actx; + static const uint8_t iv[PTLS_MAX_IV_SIZE] = {0}; + + (void)noncelen; + (void)keylen; + + actx = ptls_aead_new_direct(cipher, /* is_enc = */ 1, key, iv); + if (actx == NULL) { + return -1; + } + + aead_ctx->native_handle = actx; + + return 0; +} + +int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + const ptls_aead_algorithm_t *cipher = aead->native_handle; + size_t keylen = crypto_aead_keylen(cipher); + ptls_aead_context_t *actx; + const uint8_t iv[PTLS_MAX_IV_SIZE] = {0}; + + (void)noncelen; + (void)keylen; + + actx = ptls_aead_new_direct(cipher, /* is_enc = */ 0, key, iv); + if (actx == NULL) { + return -1; + } + + aead_ctx->native_handle = actx; + + return 0; +} + +void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) { + if (aead_ctx->native_handle) { + ptls_aead_free(aead_ctx->native_handle); + } +} + +int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, + const ngtcp2_crypto_cipher *cipher, + const uint8_t *key) { + ptls_cipher_context_t *actx; + + actx = ptls_cipher_new(cipher->native_handle, /* is_enc = */ 1, key); + if (actx == NULL) { + return -1; + } + + cipher_ctx->native_handle = actx; + + return 0; +} + +void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) { + if (cipher_ctx->native_handle) { + ptls_cipher_free(cipher_ctx->native_handle); + } +} + +int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen) { + ptls_iovec_t saltv, ikm; + + saltv = ptls_iovec_init(salt, saltlen); + ikm = ptls_iovec_init(secret, secretlen); + + if (ptls_hkdf_extract(md->native_handle, dest, saltv, ikm) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *info, + size_t infolen) { + ptls_iovec_t prk, infov; + + prk = ptls_iovec_init(secret, secretlen); + infov = ptls_iovec_init(info, infolen); + + if (ptls_hkdf_expand(md->native_handle, dest, destlen, prk, infov) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, size_t saltlen, + const uint8_t *info, size_t infolen) { + ptls_iovec_t saltv, ikm, prk, infov; + uint8_t prkbuf[PTLS_MAX_DIGEST_SIZE]; + ptls_hash_algorithm_t *algo = md->native_handle; + + saltv = ptls_iovec_init(salt, saltlen); + ikm = ptls_iovec_init(secret, secretlen); + + if (ptls_hkdf_extract(algo, prkbuf, saltv, ikm) != 0) { + return -1; + } + + prk = ptls_iovec_init(prkbuf, algo->digest_size); + infov = ptls_iovec_init(info, infolen); + + if (ptls_hkdf_expand(algo, dest, destlen, prk, infov) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + ptls_aead_context_t *actx = aead_ctx->native_handle; + + (void)aead; + + ptls_aead_xor_iv(actx, nonce, noncelen); + + ptls_aead_encrypt(actx, dest, plaintext, plaintextlen, 0, aad, aadlen); + + /* zero-out static iv once again */ + ptls_aead_xor_iv(actx, nonce, noncelen); + + return 0; +} + +int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + ptls_aead_context_t *actx = aead_ctx->native_handle; + + (void)aead; + + ptls_aead_xor_iv(actx, nonce, noncelen); + + if (ptls_aead_decrypt(actx, dest, ciphertext, ciphertextlen, 0, aad, + aadlen) == SIZE_MAX) { + return -1; + } + + /* zero-out static iv once again */ + ptls_aead_xor_iv(actx, nonce, noncelen); + + return 0; +} + +int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + ptls_cipher_context_t *actx = hp_ctx->native_handle; + static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; + + (void)hp; + + ptls_cipher_init(actx, sample); + ptls_cipher_encrypt(actx, dest, PLAINTEXT, sizeof(PLAINTEXT) - 1); + + return 0; +} + +int ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen) { + ngtcp2_crypto_picotls_ctx *cptls = ngtcp2_conn_get_tls_native_handle(conn); + ptls_buffer_t sendbuf; + size_t epoch_offsets[5] = {0}; + size_t epoch = ngtcp2_crypto_picotls_from_ngtcp2_crypto_level(crypto_level); + size_t epoch_datalen; + size_t i; + int rv; + + ptls_buffer_init(&sendbuf, (void *)"", 0); + + assert(epoch == ptls_get_read_epoch(cptls->ptls)); + + rv = ptls_handle_message(cptls->ptls, &sendbuf, epoch_offsets, epoch, data, + datalen, &cptls->handshake_properties); + if (rv != 0 && rv != PTLS_ERROR_IN_PROGRESS) { + if (PTLS_ERROR_GET_CLASS(rv) == PTLS_ERROR_CLASS_SELF_ALERT) { + ngtcp2_conn_set_tls_alert(conn, (uint8_t)PTLS_ERROR_TO_ALERT(rv)); + } + + rv = -1; + goto fin; + } + + if (!ngtcp2_conn_is_server(conn) && + cptls->handshake_properties.client.early_data_acceptance == + PTLS_EARLY_DATA_REJECTED) { + rv = ngtcp2_conn_early_data_rejected(conn); + if (rv != 0) { + rv = -1; + goto fin; + } + } + + for (i = 0; i < 4; ++i) { + epoch_datalen = epoch_offsets[i + 1] - epoch_offsets[i]; + if (epoch_datalen == 0) { + continue; + } + + assert(i != 1); + + if (ngtcp2_conn_submit_crypto_data( + conn, ngtcp2_crypto_picotls_from_epoch(i), + sendbuf.base + epoch_offsets[i], epoch_datalen) != 0) { + rv = -1; + goto fin; + } + } + + if (rv == 0) { + ngtcp2_conn_handshake_completed(conn); + } + + rv = 0; + +fin: + ptls_buffer_dispose(&sendbuf); + + return rv; +} + +int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) { + (void)conn; + (void)tls; + + /* The remote transport parameters will be set via picotls + collected_extensions callback */ + + return 0; +} + +int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf, + size_t len) { + (void)tls; + (void)buf; + (void)len; + + /* The local transport parameters will be set in an external + call. */ + + return 0; +} + +ngtcp2_crypto_level ngtcp2_crypto_picotls_from_epoch(size_t epoch) { + switch (epoch) { + case 0: + return NGTCP2_CRYPTO_LEVEL_INITIAL; + case 1: + return NGTCP2_CRYPTO_LEVEL_EARLY; + case 2: + return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + case 3: + return NGTCP2_CRYPTO_LEVEL_APPLICATION; + default: + assert(0); + abort(); + } +} + +size_t ngtcp2_crypto_picotls_from_ngtcp2_crypto_level( + ngtcp2_crypto_level crypto_level) { + switch (crypto_level) { + case NGTCP2_CRYPTO_LEVEL_INITIAL: + return 0; + case NGTCP2_CRYPTO_LEVEL_EARLY: + return 1; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + return 2; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + return 3; + default: + assert(0); + abort(); + } +} + +int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + void *user_data) { + (void)conn; + (void)user_data; + + ptls_openssl_random_bytes(data, NGTCP2_PATH_CHALLENGE_DATALEN); + + return 0; +} + +int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + ptls_openssl_random_bytes(data, datalen); + + return 0; +} + +void ngtcp2_crypto_picotls_ctx_init(ngtcp2_crypto_picotls_ctx *cptls) { + cptls->ptls = NULL; + memset(&cptls->handshake_properties, 0, sizeof(cptls->handshake_properties)); +} + +static int set_additional_extensions(ptls_handshake_properties_t *hsprops, + ngtcp2_conn *conn) { + const size_t buflen = 256; + uint8_t *buf; + ngtcp2_ssize nwrite; + ptls_raw_extension_t *exts = hsprops->additional_extensions; + + assert(exts); + + buf = malloc(buflen); + if (buf == NULL) { + return -1; + } + + nwrite = ngtcp2_conn_encode_local_transport_params(conn, buf, buflen); + if (nwrite < 0) { + goto fail; + } + + exts[0].type = NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1; + exts[0].data.base = buf; + exts[0].data.len = (size_t)nwrite; + + return 0; + +fail: + free(buf); + + return -1; +} + +int ngtcp2_crypto_picotls_collect_extension( + ptls_t *ptls, struct st_ptls_handshake_properties_t *properties, + uint16_t type) { + (void)ptls; + (void)properties; + + return type == NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1; +} + +int ngtcp2_crypto_picotls_collected_extensions( + ptls_t *ptls, struct st_ptls_handshake_properties_t *properties, + ptls_raw_extension_t *extensions) { + ngtcp2_crypto_conn_ref *conn_ref; + ngtcp2_conn *conn; + int rv; + + (void)properties; + + for (; extensions->type != UINT16_MAX; ++extensions) { + if (extensions->type != NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1) { + continue; + } + + conn_ref = *ptls_get_data_ptr(ptls); + conn = conn_ref->get_conn(conn_ref); + + rv = ngtcp2_conn_decode_remote_transport_params(conn, extensions->data.base, + extensions->data.len); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return -1; + } + + return 0; + } + + return 0; +} + +static int update_traffic_key_server_cb(ptls_update_traffic_key_t *self, + ptls_t *ptls, int is_enc, size_t epoch, + const void *secret) { + ngtcp2_crypto_conn_ref *conn_ref = *ptls_get_data_ptr(ptls); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = ngtcp2_crypto_picotls_from_epoch(epoch); + ptls_cipher_suite_t *cipher = ptls_get_cipher(ptls); + size_t secretlen = cipher->hash->digest_size; + ngtcp2_crypto_picotls_ctx *cptls; + + (void)self; + + if (is_enc) { + if (ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level, + secret, secretlen) != 0) { + return -1; + } + + if (level == NGTCP2_CRYPTO_LEVEL_HANDSHAKE) { + /* libngtcp2 allows an application to change QUIC transport + * parameters before installing Handshake tx key. We need to + * wait for the key to get the correct local transport + * parameters from ngtcp2_conn. + */ + cptls = ngtcp2_conn_get_tls_native_handle(conn); + + if (set_additional_extensions(&cptls->handshake_properties, conn) != 0) { + return -1; + } + } + + return 0; + } + + if (ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level, + secret, secretlen) != 0) { + return -1; + } + + return 0; +} + +static ptls_update_traffic_key_t update_traffic_key_server = { + update_traffic_key_server_cb, +}; + +static int update_traffic_key_cb(ptls_update_traffic_key_t *self, ptls_t *ptls, + int is_enc, size_t epoch, const void *secret) { + ngtcp2_crypto_conn_ref *conn_ref = *ptls_get_data_ptr(ptls); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = ngtcp2_crypto_picotls_from_epoch(epoch); + ptls_cipher_suite_t *cipher = ptls_get_cipher(ptls); + size_t secretlen = cipher->hash->digest_size; + + (void)self; + + if (is_enc) { + if (ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level, + secret, secretlen) != 0) { + return -1; + } + + return 0; + } + + if (ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level, + secret, secretlen) != 0) { + return -1; + } + + return 0; +} + +static ptls_update_traffic_key_t update_traffic_key = {update_traffic_key_cb}; + +int ngtcp2_crypto_picotls_configure_server_context(ptls_context_t *ctx) { + ctx->max_early_data_size = UINT32_MAX; + ctx->omit_end_of_early_data = 1; + ctx->update_traffic_key = &update_traffic_key_server; + + return 0; +} + +int ngtcp2_crypto_picotls_configure_client_context(ptls_context_t *ctx) { + ctx->omit_end_of_early_data = 1; + ctx->update_traffic_key = &update_traffic_key; + + return 0; +} + +int ngtcp2_crypto_picotls_configure_server_session( + ngtcp2_crypto_picotls_ctx *cptls) { + ptls_handshake_properties_t *hsprops = &cptls->handshake_properties; + + hsprops->collect_extension = ngtcp2_crypto_picotls_collect_extension; + hsprops->collected_extensions = ngtcp2_crypto_picotls_collected_extensions; + + return 0; +} + +int ngtcp2_crypto_picotls_configure_client_session( + ngtcp2_crypto_picotls_ctx *cptls, ngtcp2_conn *conn) { + ptls_handshake_properties_t *hsprops = &cptls->handshake_properties; + + hsprops->client.max_early_data_size = calloc(1, sizeof(uint32_t)); + if (hsprops->client.max_early_data_size == NULL) { + return -1; + } + + if (set_additional_extensions(hsprops, conn) != 0) { + free(hsprops->client.max_early_data_size); + hsprops->client.max_early_data_size = NULL; + return -1; + } + + hsprops->collect_extension = ngtcp2_crypto_picotls_collect_extension; + hsprops->collected_extensions = ngtcp2_crypto_picotls_collected_extensions; + + return 0; +} + +void ngtcp2_crypto_picotls_deconfigure_session( + ngtcp2_crypto_picotls_ctx *cptls) { + ptls_handshake_properties_t *hsprops; + ptls_raw_extension_t *exts; + + if (cptls == NULL) { + return; + } + + hsprops = &cptls->handshake_properties; + + free(hsprops->client.max_early_data_size); + + exts = hsprops->additional_extensions; + if (exts) { + free(hsprops->additional_extensions[0].data.base); + } +} diff --git a/crypto/shared.c b/crypto/shared.c new file mode 100644 index 0000000..78252b8 --- /dev/null +++ b/crypto/shared.c @@ -0,0 +1,1418 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shared.h" + +#ifdef WIN32 +# include <winsock2.h> +# include <ws2tcpip.h> +#else +# include <netinet/in.h> +#endif + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_macro.h" +#include "ngtcp2_net.h" + +ngtcp2_crypto_md *ngtcp2_crypto_md_init(ngtcp2_crypto_md *md, + void *md_native_handle) { + md->native_handle = md_native_handle; + return md; +} + +int ngtcp2_crypto_hkdf_expand_label(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *label, size_t labellen) { + static const uint8_t LABEL[] = "tls13 "; + uint8_t info[256]; + uint8_t *p = info; + + *p++ = (uint8_t)(destlen / 256); + *p++ = (uint8_t)(destlen % 256); + *p++ = (uint8_t)(sizeof(LABEL) - 1 + labellen); + memcpy(p, LABEL, sizeof(LABEL) - 1); + p += sizeof(LABEL) - 1; + memcpy(p, label, labellen); + p += labellen; + *p++ = 0; + + return ngtcp2_crypto_hkdf_expand(dest, destlen, md, secret, secretlen, info, + (size_t)(p - info)); +} + +#define NGTCP2_CRYPTO_INITIAL_SECRETLEN 32 + +int ngtcp2_crypto_derive_initial_secrets(uint32_t version, uint8_t *rx_secret, + uint8_t *tx_secret, + uint8_t *initial_secret, + const ngtcp2_cid *client_dcid, + ngtcp2_crypto_side side) { + static const uint8_t CLABEL[] = "client in"; + static const uint8_t SLABEL[] = "server in"; + uint8_t initial_secret_buf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t *client_secret; + uint8_t *server_secret; + ngtcp2_crypto_ctx ctx; + const uint8_t *salt; + size_t saltlen; + + if (!initial_secret) { + initial_secret = initial_secret_buf; + } + + ngtcp2_crypto_ctx_initial(&ctx); + + switch (version) { + case NGTCP2_PROTO_VER_V1: + salt = (const uint8_t *)NGTCP2_INITIAL_SALT_V1; + saltlen = sizeof(NGTCP2_INITIAL_SALT_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2_DRAFT: + salt = (const uint8_t *)NGTCP2_INITIAL_SALT_V2_DRAFT; + saltlen = sizeof(NGTCP2_INITIAL_SALT_V2_DRAFT) - 1; + break; + default: + salt = (const uint8_t *)NGTCP2_INITIAL_SALT_DRAFT; + saltlen = sizeof(NGTCP2_INITIAL_SALT_DRAFT) - 1; + } + + if (ngtcp2_crypto_hkdf_extract(initial_secret, &ctx.md, client_dcid->data, + client_dcid->datalen, salt, saltlen) != 0) { + return -1; + } + + if (side == NGTCP2_CRYPTO_SIDE_SERVER) { + client_secret = rx_secret; + server_secret = tx_secret; + } else { + client_secret = tx_secret; + server_secret = rx_secret; + } + + if (ngtcp2_crypto_hkdf_expand_label( + client_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, &ctx.md, + initial_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, CLABEL, + sizeof(CLABEL) - 1) != 0 || + ngtcp2_crypto_hkdf_expand_label( + server_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, &ctx.md, + initial_secret, NGTCP2_CRYPTO_INITIAL_SECRETLEN, SLABEL, + sizeof(SLABEL) - 1) != 0) { + return -1; + } + + return 0; +} + +size_t ngtcp2_crypto_packet_protection_ivlen(const ngtcp2_crypto_aead *aead) { + size_t noncelen = ngtcp2_crypto_aead_noncelen(aead); + return ngtcp2_max(8, noncelen); +} + +int ngtcp2_crypto_derive_packet_protection_key( + uint8_t *key, uint8_t *iv, uint8_t *hp_key, uint32_t version, + const ngtcp2_crypto_aead *aead, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen) { + static const uint8_t KEY_LABEL_V1[] = "quic key"; + static const uint8_t IV_LABEL_V1[] = "quic iv"; + static const uint8_t HP_KEY_LABEL_V1[] = "quic hp"; + static const uint8_t KEY_LABEL_V2_DRAFT[] = "quicv2 key"; + static const uint8_t IV_LABEL_V2_DRAFT[] = "quicv2 iv"; + static const uint8_t HP_KEY_LABEL_V2_DRAFT[] = "quicv2 hp"; + size_t keylen = ngtcp2_crypto_aead_keylen(aead); + size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + const uint8_t *key_label; + size_t key_labellen; + const uint8_t *iv_label; + size_t iv_labellen; + const uint8_t *hp_key_label; + size_t hp_key_labellen; + + switch (version) { + case NGTCP2_PROTO_VER_V2_DRAFT: + key_label = KEY_LABEL_V2_DRAFT; + key_labellen = sizeof(KEY_LABEL_V2_DRAFT) - 1; + iv_label = IV_LABEL_V2_DRAFT; + iv_labellen = sizeof(IV_LABEL_V2_DRAFT) - 1; + hp_key_label = HP_KEY_LABEL_V2_DRAFT; + hp_key_labellen = sizeof(HP_KEY_LABEL_V2_DRAFT) - 1; + break; + default: + key_label = KEY_LABEL_V1; + key_labellen = sizeof(KEY_LABEL_V1) - 1; + iv_label = IV_LABEL_V1; + iv_labellen = sizeof(IV_LABEL_V1) - 1; + hp_key_label = HP_KEY_LABEL_V1; + hp_key_labellen = sizeof(HP_KEY_LABEL_V1) - 1; + } + + if (ngtcp2_crypto_hkdf_expand_label(key, keylen, md, secret, secretlen, + key_label, key_labellen) != 0) { + return -1; + } + + if (ngtcp2_crypto_hkdf_expand_label(iv, ivlen, md, secret, secretlen, + iv_label, iv_labellen) != 0) { + return -1; + } + + if (hp_key != NULL && + ngtcp2_crypto_hkdf_expand_label(hp_key, keylen, md, secret, secretlen, + hp_key_label, hp_key_labellen) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_update_traffic_secret(uint8_t *dest, + const ngtcp2_crypto_md *md, + const uint8_t *secret, + size_t secretlen) { + static const uint8_t LABEL[] = "quic ku"; + + if (ngtcp2_crypto_hkdf_expand_label(dest, secretlen, md, secret, secretlen, + LABEL, sizeof(LABEL) - 1) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_derive_and_install_rx_key(ngtcp2_conn *conn, uint8_t *key, + uint8_t *iv, uint8_t *hp_key, + ngtcp2_crypto_level level, + const uint8_t *secret, + size_t secretlen) { + const ngtcp2_crypto_ctx *ctx; + const ngtcp2_crypto_aead *aead; + const ngtcp2_crypto_md *md; + const ngtcp2_crypto_cipher *hp; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + void *tls = ngtcp2_conn_get_tls_native_handle(conn); + uint8_t keybuf[64], ivbuf[64], hp_keybuf[64]; + size_t ivlen; + int rv; + ngtcp2_crypto_ctx cctx; + uint32_t version; + + if (level == NGTCP2_CRYPTO_LEVEL_EARLY && !ngtcp2_conn_is_server(conn)) { + return 0; + } + + if (!key) { + key = keybuf; + } + if (!iv) { + iv = ivbuf; + } + if (!hp_key) { + hp_key = hp_keybuf; + } + + switch (level) { + case NGTCP2_CRYPTO_LEVEL_EARLY: + ngtcp2_crypto_ctx_tls_early(&cctx, tls); + ngtcp2_conn_set_early_crypto_ctx(conn, &cctx); + ctx = ngtcp2_conn_get_early_crypto_ctx(conn); + version = ngtcp2_conn_get_client_chosen_version(conn); + break; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + if (ngtcp2_conn_is_server(conn) && + !ngtcp2_conn_get_negotiated_version(conn)) { + rv = ngtcp2_crypto_set_remote_transport_params(conn, tls); + if (rv != 0) { + return -1; + } + } + /* fall through */ + default: + ctx = ngtcp2_conn_get_crypto_ctx(conn); + version = ngtcp2_conn_get_negotiated_version(conn); + + if (!ctx->aead.native_handle) { + ngtcp2_crypto_ctx_tls(&cctx, tls); + ngtcp2_conn_set_crypto_ctx(conn, &cctx); + ctx = ngtcp2_conn_get_crypto_ctx(conn); + } + } + + aead = &ctx->aead; + md = &ctx->md; + hp = &ctx->hp; + ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + + if (ngtcp2_crypto_derive_packet_protection_key(key, iv, hp_key, version, aead, + md, secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, aead, key, ivlen) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, hp, hp_key) != 0) { + goto fail; + } + + switch (level) { + case NGTCP2_CRYPTO_LEVEL_EARLY: + rv = ngtcp2_conn_install_early_key(conn, &aead_ctx, iv, ivlen, &hp_ctx); + if (rv != 0) { + goto fail; + } + break; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + rv = ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, iv, ivlen, + &hp_ctx); + if (rv != 0) { + goto fail; + } + break; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + if (!ngtcp2_conn_is_server(conn)) { + rv = ngtcp2_crypto_set_remote_transport_params(conn, tls); + if (rv != 0) { + goto fail; + } + } + + rv = ngtcp2_conn_install_rx_key(conn, secret, secretlen, &aead_ctx, iv, + ivlen, &hp_ctx); + if (rv != 0) { + goto fail; + } + + break; + default: + goto fail; + } + + return 0; + +fail: + ngtcp2_crypto_cipher_ctx_free(&hp_ctx); + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + return -1; +} + +/* + * crypto_set_local_transport_params gets local QUIC transport + * parameters from |conn| and sets it to |tls|. + * + * This function returns 0 if it succeeds, or -1. + */ +static int crypto_set_local_transport_params(ngtcp2_conn *conn, void *tls) { + ngtcp2_ssize nwrite; + uint8_t buf[256]; + + nwrite = ngtcp2_conn_encode_local_transport_params(conn, buf, sizeof(buf)); + if (nwrite < 0) { + return -1; + } + + if (ngtcp2_crypto_set_local_transport_params(tls, buf, (size_t)nwrite) != 0) { + return -1; + } + + return 0; +} + +int ngtcp2_crypto_derive_and_install_tx_key(ngtcp2_conn *conn, uint8_t *key, + uint8_t *iv, uint8_t *hp_key, + ngtcp2_crypto_level level, + const uint8_t *secret, + size_t secretlen) { + const ngtcp2_crypto_ctx *ctx; + const ngtcp2_crypto_aead *aead; + const ngtcp2_crypto_md *md; + const ngtcp2_crypto_cipher *hp; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + void *tls = ngtcp2_conn_get_tls_native_handle(conn); + uint8_t keybuf[64], ivbuf[64], hp_keybuf[64]; + size_t ivlen; + int rv; + ngtcp2_crypto_ctx cctx; + uint32_t version; + + if (level == NGTCP2_CRYPTO_LEVEL_EARLY && ngtcp2_conn_is_server(conn)) { + return 0; + } + + if (!key) { + key = keybuf; + } + if (!iv) { + iv = ivbuf; + } + if (!hp_key) { + hp_key = hp_keybuf; + } + + switch (level) { + case NGTCP2_CRYPTO_LEVEL_EARLY: + ngtcp2_crypto_ctx_tls_early(&cctx, tls); + ngtcp2_conn_set_early_crypto_ctx(conn, &cctx); + ctx = ngtcp2_conn_get_early_crypto_ctx(conn); + version = ngtcp2_conn_get_client_chosen_version(conn); + break; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + if (ngtcp2_conn_is_server(conn) && + !ngtcp2_conn_get_negotiated_version(conn)) { + rv = ngtcp2_crypto_set_remote_transport_params(conn, tls); + if (rv != 0) { + return -1; + } + } + /* fall through */ + default: + ctx = ngtcp2_conn_get_crypto_ctx(conn); + version = ngtcp2_conn_get_negotiated_version(conn); + + if (!ctx->aead.native_handle) { + ngtcp2_crypto_ctx_tls(&cctx, tls); + ngtcp2_conn_set_crypto_ctx(conn, &cctx); + ctx = ngtcp2_conn_get_crypto_ctx(conn); + } + } + + aead = &ctx->aead; + md = &ctx->md; + hp = &ctx->hp; + ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + + if (ngtcp2_crypto_derive_packet_protection_key(key, iv, hp_key, version, aead, + md, secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, aead, key, ivlen) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, hp, hp_key) != 0) { + goto fail; + } + + switch (level) { + case NGTCP2_CRYPTO_LEVEL_EARLY: + rv = ngtcp2_conn_install_early_key(conn, &aead_ctx, iv, ivlen, &hp_ctx); + if (rv != 0) { + goto fail; + } + break; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + rv = ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, iv, ivlen, + &hp_ctx); + if (rv != 0) { + goto fail; + } + + if (ngtcp2_conn_is_server(conn) && + crypto_set_local_transport_params(conn, tls) != 0) { + goto fail; + } + + break; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + rv = ngtcp2_conn_install_tx_key(conn, secret, secretlen, &aead_ctx, iv, + ivlen, &hp_ctx); + if (rv != 0) { + goto fail; + } + + break; + default: + goto fail; + } + + return 0; + +fail: + ngtcp2_crypto_cipher_ctx_free(&hp_ctx); + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + return -1; +} + +int ngtcp2_crypto_derive_and_install_initial_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, + uint8_t *rx_hp_key, uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp_key, + uint32_t version, const ngtcp2_cid *client_dcid) { + uint8_t rx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t tx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t initial_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t rx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t rx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t rx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t tx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + ngtcp2_crypto_ctx ctx; + ngtcp2_crypto_aead retry_aead; + ngtcp2_crypto_aead_ctx rx_aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx rx_hp_ctx = {0}; + ngtcp2_crypto_aead_ctx tx_aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx tx_hp_ctx = {0}; + ngtcp2_crypto_aead_ctx retry_aead_ctx = {0}; + int rv; + int server = ngtcp2_conn_is_server(conn); + const uint8_t *retry_key; + size_t retry_noncelen; + + ngtcp2_crypto_ctx_initial(&ctx); + + if (!rx_secret) { + rx_secret = rx_secretbuf; + } + if (!tx_secret) { + tx_secret = tx_secretbuf; + } + if (!initial_secret) { + initial_secret = initial_secretbuf; + } + + if (!rx_key) { + rx_key = rx_keybuf; + } + if (!rx_iv) { + rx_iv = rx_ivbuf; + } + if (!rx_hp_key) { + rx_hp_key = rx_hp_keybuf; + } + if (!tx_key) { + tx_key = tx_keybuf; + } + if (!tx_iv) { + tx_iv = tx_ivbuf; + } + if (!tx_hp_key) { + tx_hp_key = tx_hp_keybuf; + } + + ngtcp2_conn_set_initial_crypto_ctx(conn, &ctx); + + if (ngtcp2_crypto_derive_initial_secrets( + version, rx_secret, tx_secret, initial_secret, client_dcid, + server ? NGTCP2_CRYPTO_SIDE_SERVER : NGTCP2_CRYPTO_SIDE_CLIENT) != + 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + rx_key, rx_iv, rx_hp_key, version, &ctx.aead, &ctx.md, rx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + tx_key, tx_iv, tx_hp_key, version, &ctx.aead, &ctx.md, tx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&rx_aead_ctx, &ctx.aead, rx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&rx_hp_ctx, &ctx.hp, rx_hp_key) != + 0) { + goto fail; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&tx_aead_ctx, &ctx.aead, tx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&tx_hp_ctx, &ctx.hp, tx_hp_key) != + 0) { + goto fail; + } + + if (!server && !ngtcp2_conn_after_retry(conn)) { + ngtcp2_crypto_aead_retry(&retry_aead); + + switch (version) { + case NGTCP2_PROTO_VER_V1: + retry_key = (const uint8_t *)NGTCP2_RETRY_KEY_V1; + retry_noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2_DRAFT: + retry_key = (const uint8_t *)NGTCP2_RETRY_KEY_V2_DRAFT; + retry_noncelen = sizeof(NGTCP2_RETRY_NONCE_V2_DRAFT) - 1; + break; + default: + retry_key = (const uint8_t *)NGTCP2_RETRY_KEY_DRAFT; + retry_noncelen = sizeof(NGTCP2_RETRY_NONCE_DRAFT) - 1; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&retry_aead_ctx, &retry_aead, + retry_key, retry_noncelen) != 0) { + goto fail; + } + } + + rv = ngtcp2_conn_install_initial_key(conn, &rx_aead_ctx, rx_iv, &rx_hp_ctx, + &tx_aead_ctx, tx_iv, &tx_hp_ctx, + NGTCP2_CRYPTO_INITIAL_IVLEN); + if (rv != 0) { + goto fail; + } + + if (retry_aead_ctx.native_handle) { + ngtcp2_conn_set_retry_aead(conn, &retry_aead, &retry_aead_ctx); + } + + return 0; + +fail: + ngtcp2_crypto_aead_ctx_free(&retry_aead_ctx); + ngtcp2_crypto_cipher_ctx_free(&tx_hp_ctx); + ngtcp2_crypto_aead_ctx_free(&tx_aead_ctx); + ngtcp2_crypto_cipher_ctx_free(&rx_hp_ctx); + ngtcp2_crypto_aead_ctx_free(&rx_aead_ctx); + + return -1; +} + +int ngtcp2_crypto_derive_and_install_vneg_initial_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, + uint8_t *rx_hp_key, uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp_key, + uint32_t version, const ngtcp2_cid *client_dcid) { + uint8_t rx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t tx_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t initial_secretbuf[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t rx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t rx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t rx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_ivbuf[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t tx_hp_keybuf[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + const ngtcp2_crypto_ctx *ctx = ngtcp2_conn_get_initial_crypto_ctx(conn); + ngtcp2_crypto_aead_ctx rx_aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx rx_hp_ctx = {0}; + ngtcp2_crypto_aead_ctx tx_aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx tx_hp_ctx = {0}; + int rv; + int server = ngtcp2_conn_is_server(conn); + + if (!rx_secret) { + rx_secret = rx_secretbuf; + } + if (!tx_secret) { + tx_secret = tx_secretbuf; + } + if (!initial_secret) { + initial_secret = initial_secretbuf; + } + + if (!rx_key) { + rx_key = rx_keybuf; + } + if (!rx_iv) { + rx_iv = rx_ivbuf; + } + if (!rx_hp_key) { + rx_hp_key = rx_hp_keybuf; + } + if (!tx_key) { + tx_key = tx_keybuf; + } + if (!tx_iv) { + tx_iv = tx_ivbuf; + } + if (!tx_hp_key) { + tx_hp_key = tx_hp_keybuf; + } + + if (ngtcp2_crypto_derive_initial_secrets( + version, rx_secret, tx_secret, initial_secret, client_dcid, + server ? NGTCP2_CRYPTO_SIDE_SERVER : NGTCP2_CRYPTO_SIDE_CLIENT) != + 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + rx_key, rx_iv, rx_hp_key, version, &ctx->aead, &ctx->md, rx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + tx_key, tx_iv, tx_hp_key, version, &ctx->aead, &ctx->md, tx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&rx_aead_ctx, &ctx->aead, rx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&rx_hp_ctx, &ctx->hp, rx_hp_key) != + 0) { + goto fail; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&tx_aead_ctx, &ctx->aead, tx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + goto fail; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&tx_hp_ctx, &ctx->hp, tx_hp_key) != + 0) { + goto fail; + } + + rv = ngtcp2_conn_install_vneg_initial_key( + conn, version, &rx_aead_ctx, rx_iv, &rx_hp_ctx, &tx_aead_ctx, tx_iv, + &tx_hp_ctx, NGTCP2_CRYPTO_INITIAL_IVLEN); + if (rv != 0) { + goto fail; + } + + return 0; + +fail: + ngtcp2_crypto_cipher_ctx_free(&tx_hp_ctx); + ngtcp2_crypto_aead_ctx_free(&tx_aead_ctx); + ngtcp2_crypto_cipher_ctx_free(&rx_hp_ctx); + ngtcp2_crypto_aead_ctx_free(&rx_aead_ctx); + + return -1; +} + +int ngtcp2_crypto_update_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_key, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_key, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen) { + const ngtcp2_crypto_ctx *ctx = ngtcp2_conn_get_crypto_ctx(conn); + const ngtcp2_crypto_aead *aead = &ctx->aead; + const ngtcp2_crypto_md *md = &ctx->md; + size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + uint32_t version = ngtcp2_conn_get_negotiated_version(conn); + + if (ngtcp2_crypto_update_traffic_secret(rx_secret, md, current_rx_secret, + secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + rx_key, rx_iv, NULL, version, aead, md, rx_secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_update_traffic_secret(tx_secret, md, current_tx_secret, + secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + tx_key, tx_iv, NULL, version, aead, md, tx_secret, secretlen) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_decrypt_init(rx_aead_ctx, aead, rx_key, ivlen) != + 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(tx_aead_ctx, aead, tx_key, ivlen) != + 0) { + ngtcp2_crypto_aead_ctx_free(rx_aead_ctx); + rx_aead_ctx->native_handle = NULL; + return -1; + } + + return 0; +} + +int ngtcp2_crypto_encrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + if (ngtcp2_crypto_encrypt(dest, aead, aead_ctx, plaintext, plaintextlen, + nonce, noncelen, aad, aadlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +int ngtcp2_crypto_decrypt_cb(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + if (ngtcp2_crypto_decrypt(dest, aead, aead_ctx, ciphertext, ciphertextlen, + nonce, noncelen, aad, aadlen) != 0) { + return NGTCP2_ERR_DECRYPT; + } + return 0; +} + +int ngtcp2_crypto_hp_mask_cb(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +int ngtcp2_crypto_update_key_cb( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen, void *user_data) { + uint8_t rx_key[64]; + uint8_t tx_key[64]; + (void)conn; + (void)user_data; + + if (ngtcp2_crypto_update_key(conn, rx_secret, tx_secret, rx_aead_ctx, rx_key, + rx_iv, tx_aead_ctx, tx_key, tx_iv, + current_rx_secret, current_tx_secret, + secretlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +int ngtcp2_crypto_generate_stateless_reset_token(uint8_t *token, + const uint8_t *secret, + size_t secretlen, + const ngtcp2_cid *cid) { + static const uint8_t info[] = "stateless_reset"; + ngtcp2_crypto_md md; + + if (ngtcp2_crypto_hkdf(token, NGTCP2_STATELESS_RESET_TOKENLEN, + ngtcp2_crypto_md_sha256(&md), secret, secretlen, + cid->data, cid->datalen, info, + sizeof(info) - 1) != 0) { + return -1; + } + + return 0; +} + +static int crypto_derive_token_key(uint8_t *key, size_t keylen, uint8_t *iv, + size_t ivlen, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen, + const uint8_t *info_prefix, + size_t info_prefixlen) { + static const uint8_t key_info_suffix[] = " key"; + static const uint8_t iv_info_suffix[] = " iv"; + uint8_t intsecret[32]; + uint8_t info[32]; + uint8_t *p; + + assert(ngtcp2_crypto_md_hashlen(md) == sizeof(intsecret)); + assert(info_prefixlen + sizeof(key_info_suffix) - 1 <= sizeof(info)); + assert(info_prefixlen + sizeof(iv_info_suffix) - 1 <= sizeof(info)); + + if (ngtcp2_crypto_hkdf_extract(intsecret, md, secret, secretlen, salt, + saltlen) != 0) { + return -1; + } + + memcpy(info, info_prefix, info_prefixlen); + p = info + info_prefixlen; + + memcpy(p, key_info_suffix, sizeof(key_info_suffix) - 1); + p += sizeof(key_info_suffix) - 1; + + if (ngtcp2_crypto_hkdf_expand(key, keylen, md, intsecret, sizeof(intsecret), + info, (size_t)(p - info)) != 0) { + return -1; + } + + p = info + info_prefixlen; + + memcpy(p, iv_info_suffix, sizeof(iv_info_suffix) - 1); + p += sizeof(iv_info_suffix) - 1; + + if (ngtcp2_crypto_hkdf_expand(iv, ivlen, md, intsecret, sizeof(intsecret), + info, (size_t)(p - info)) != 0) { + return -1; + } + + return 0; +} + +static size_t crypto_generate_retry_token_aad(uint8_t *dest, uint32_t version, + const ngtcp2_sockaddr *sa, + ngtcp2_socklen salen, + const ngtcp2_cid *retry_scid) { + uint8_t *p = dest; + + version = ngtcp2_htonl(version); + memcpy(p, &version, sizeof(version)); + memcpy(p, sa, (size_t)salen); + p += salen; + memcpy(p, retry_scid->data, retry_scid->datalen); + p += retry_scid->datalen; + + return (size_t)(p - dest); +} + +static const uint8_t retry_token_info_prefix[] = "retry_token"; + +ngtcp2_ssize ngtcp2_crypto_generate_retry_token( + uint8_t *token, const uint8_t *secret, size_t secretlen, uint32_t version, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + const ngtcp2_cid *retry_scid, const ngtcp2_cid *odcid, ngtcp2_tstamp ts) { + uint8_t plaintext[NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN]; + uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN]; + uint8_t key[32]; + uint8_t iv[32]; + size_t keylen; + size_t ivlen; + ngtcp2_crypto_aead aead; + ngtcp2_crypto_md md; + ngtcp2_crypto_aead_ctx aead_ctx; + size_t plaintextlen; + uint8_t aad[sizeof(version) + sizeof(ngtcp2_sockaddr_storage) + + NGTCP2_MAX_CIDLEN]; + size_t aadlen; + uint8_t *p = plaintext; + ngtcp2_tstamp ts_be = ngtcp2_htonl64(ts); + int rv; + + memset(plaintext, 0, sizeof(plaintext)); + + *p++ = (uint8_t)odcid->datalen; + memcpy(p, odcid->data, odcid->datalen); + p += NGTCP2_MAX_CIDLEN; + memcpy(p, &ts_be, sizeof(ts_be)); + p += sizeof(ts_be); + + plaintextlen = (size_t)(p - plaintext); + + if (ngtcp2_crypto_random(rand_data, sizeof(rand_data)) != 0) { + return -1; + } + + ngtcp2_crypto_aead_aes_128_gcm(&aead); + ngtcp2_crypto_md_sha256(&md); + + keylen = ngtcp2_crypto_aead_keylen(&aead); + ivlen = ngtcp2_crypto_aead_noncelen(&aead); + + assert(sizeof(key) >= keylen); + assert(sizeof(iv) >= ivlen); + + if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, + rand_data, sizeof(rand_data), + retry_token_info_prefix, + sizeof(retry_token_info_prefix) - 1) != 0) { + return -1; + } + + aadlen = crypto_generate_retry_token_aad(aad, version, remote_addr, + remote_addrlen, retry_scid); + + p = token; + *p++ = NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY; + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, ivlen) != 0) { + return -1; + } + + rv = ngtcp2_crypto_encrypt(p, &aead, &aead_ctx, plaintext, plaintextlen, iv, + ivlen, aad, aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + p += plaintextlen + aead.max_overhead; + memcpy(p, rand_data, sizeof(rand_data)); + p += sizeof(rand_data); + + return p - token; +} + +int ngtcp2_crypto_verify_retry_token( + ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen, + const uint8_t *secret, size_t secretlen, uint32_t version, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + const ngtcp2_cid *dcid, ngtcp2_duration timeout, ngtcp2_tstamp ts) { + uint8_t + plaintext[/* cid len = */ 1 + NGTCP2_MAX_CIDLEN + sizeof(ngtcp2_tstamp)]; + uint8_t key[32]; + uint8_t iv[32]; + size_t keylen; + size_t ivlen; + ngtcp2_crypto_aead_ctx aead_ctx; + ngtcp2_crypto_aead aead; + ngtcp2_crypto_md md; + uint8_t aad[sizeof(version) + sizeof(ngtcp2_sockaddr_storage) + + NGTCP2_MAX_CIDLEN]; + size_t aadlen; + const uint8_t *rand_data; + const uint8_t *ciphertext; + size_t ciphertextlen; + size_t cil; + int rv; + ngtcp2_tstamp gen_ts; + + if (tokenlen != NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN || + token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY) { + return -1; + } + + rand_data = token + tokenlen - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN; + ciphertext = token + 1; + ciphertextlen = tokenlen - 1 - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN; + + ngtcp2_crypto_aead_aes_128_gcm(&aead); + ngtcp2_crypto_md_sha256(&md); + + keylen = ngtcp2_crypto_aead_keylen(&aead); + ivlen = ngtcp2_crypto_aead_noncelen(&aead); + + if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, + rand_data, NGTCP2_CRYPTO_TOKEN_RAND_DATALEN, + retry_token_info_prefix, + sizeof(retry_token_info_prefix) - 1) != 0) { + return -1; + } + + aadlen = crypto_generate_retry_token_aad(aad, version, remote_addr, + remote_addrlen, dcid); + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &aead, key, ivlen) != 0) { + return -1; + } + + rv = ngtcp2_crypto_decrypt(plaintext, &aead, &aead_ctx, ciphertext, + ciphertextlen, iv, ivlen, aad, aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + cil = plaintext[0]; + + assert(cil == 0 || (cil >= NGTCP2_MIN_CIDLEN && cil <= NGTCP2_MAX_CIDLEN)); + + memcpy(&gen_ts, plaintext + /* cid len = */ 1 + NGTCP2_MAX_CIDLEN, + sizeof(gen_ts)); + + gen_ts = ngtcp2_ntohl64(gen_ts); + if (gen_ts + timeout <= ts) { + return -1; + } + + ngtcp2_cid_init(odcid, plaintext + /* cid len = */ 1, cil); + + return 0; +} + +static size_t crypto_generate_regular_token_aad(uint8_t *dest, + const ngtcp2_sockaddr *sa) { + const uint8_t *addr; + size_t addrlen; + + switch (sa->sa_family) { + case AF_INET: + addr = (const uint8_t *)&((const ngtcp2_sockaddr_in *)(void *)sa)->sin_addr; + addrlen = sizeof(((const ngtcp2_sockaddr_in *)(void *)sa)->sin_addr); + break; + case AF_INET6: + addr = + (const uint8_t *)&((const ngtcp2_sockaddr_in6 *)(void *)sa)->sin6_addr; + addrlen = sizeof(((const ngtcp2_sockaddr_in6 *)(void *)sa)->sin6_addr); + break; + default: + assert(0); + abort(); + } + + memcpy(dest, addr, addrlen); + + return addrlen; +} + +static const uint8_t regular_token_info_prefix[] = "regular_token"; + +ngtcp2_ssize ngtcp2_crypto_generate_regular_token( + uint8_t *token, const uint8_t *secret, size_t secretlen, + const ngtcp2_sockaddr *remote_addr, ngtcp2_socklen remote_addrlen, + ngtcp2_tstamp ts) { + uint8_t plaintext[sizeof(ngtcp2_tstamp)]; + uint8_t rand_data[NGTCP2_CRYPTO_TOKEN_RAND_DATALEN]; + uint8_t key[32]; + uint8_t iv[32]; + size_t keylen; + size_t ivlen; + ngtcp2_crypto_aead aead; + ngtcp2_crypto_md md; + ngtcp2_crypto_aead_ctx aead_ctx; + size_t plaintextlen; + uint8_t aad[sizeof(ngtcp2_sockaddr_in6)]; + size_t aadlen; + uint8_t *p = plaintext; + ngtcp2_tstamp ts_be = ngtcp2_htonl64(ts); + int rv; + (void)remote_addrlen; + + memcpy(p, &ts_be, sizeof(ts_be)); + p += sizeof(ts_be); + + plaintextlen = (size_t)(p - plaintext); + + if (ngtcp2_crypto_random(rand_data, sizeof(rand_data)) != 0) { + return -1; + } + + ngtcp2_crypto_aead_aes_128_gcm(&aead); + ngtcp2_crypto_md_sha256(&md); + + keylen = ngtcp2_crypto_aead_keylen(&aead); + ivlen = ngtcp2_crypto_aead_noncelen(&aead); + + assert(sizeof(key) >= keylen); + assert(sizeof(iv) >= ivlen); + + if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, + rand_data, sizeof(rand_data), + regular_token_info_prefix, + sizeof(regular_token_info_prefix) - 1) != 0) { + return -1; + } + + aadlen = crypto_generate_regular_token_aad(aad, remote_addr); + + p = token; + *p++ = NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR; + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, ivlen) != 0) { + return -1; + } + + rv = ngtcp2_crypto_encrypt(p, &aead, &aead_ctx, plaintext, plaintextlen, iv, + ivlen, aad, aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + p += plaintextlen + aead.max_overhead; + memcpy(p, rand_data, sizeof(rand_data)); + p += sizeof(rand_data); + + return p - token; +} + +int ngtcp2_crypto_verify_regular_token(const uint8_t *token, size_t tokenlen, + const uint8_t *secret, size_t secretlen, + const ngtcp2_sockaddr *remote_addr, + ngtcp2_socklen remote_addrlen, + ngtcp2_duration timeout, + ngtcp2_tstamp ts) { + uint8_t plaintext[sizeof(ngtcp2_tstamp)]; + uint8_t key[32]; + uint8_t iv[32]; + size_t keylen; + size_t ivlen; + ngtcp2_crypto_aead_ctx aead_ctx; + ngtcp2_crypto_aead aead; + ngtcp2_crypto_md md; + uint8_t aad[sizeof(ngtcp2_sockaddr_in6)]; + size_t aadlen; + const uint8_t *rand_data; + const uint8_t *ciphertext; + size_t ciphertextlen; + int rv; + ngtcp2_tstamp gen_ts; + (void)remote_addrlen; + + if (tokenlen != NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN || + token[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR) { + return -1; + } + + rand_data = token + tokenlen - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN; + ciphertext = token + 1; + ciphertextlen = tokenlen - 1 - NGTCP2_CRYPTO_TOKEN_RAND_DATALEN; + + ngtcp2_crypto_aead_aes_128_gcm(&aead); + ngtcp2_crypto_md_sha256(&md); + + keylen = ngtcp2_crypto_aead_keylen(&aead); + ivlen = ngtcp2_crypto_aead_noncelen(&aead); + + if (crypto_derive_token_key(key, keylen, iv, ivlen, &md, secret, secretlen, + rand_data, NGTCP2_CRYPTO_TOKEN_RAND_DATALEN, + regular_token_info_prefix, + sizeof(regular_token_info_prefix) - 1) != 0) { + return -1; + } + + aadlen = crypto_generate_regular_token_aad(aad, remote_addr); + + if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &aead, key, ivlen) != 0) { + return -1; + } + + rv = ngtcp2_crypto_decrypt(plaintext, &aead, &aead_ctx, ciphertext, + ciphertextlen, iv, ivlen, aad, aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + memcpy(&gen_ts, plaintext, sizeof(gen_ts)); + + gen_ts = ngtcp2_ntohl64(gen_ts); + if (gen_ts + timeout <= ts) { + return -1; + } + + return 0; +} + +ngtcp2_ssize ngtcp2_crypto_write_connection_close( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, uint64_t error_code, const uint8_t *reason, + size_t reasonlen) { + uint8_t rx_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t tx_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t initial_secret[NGTCP2_CRYPTO_INITIAL_SECRETLEN]; + uint8_t tx_key[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + uint8_t tx_iv[NGTCP2_CRYPTO_INITIAL_IVLEN]; + uint8_t tx_hp_key[NGTCP2_CRYPTO_INITIAL_KEYLEN]; + ngtcp2_crypto_ctx ctx; + ngtcp2_ssize spktlen; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + + ngtcp2_crypto_ctx_initial(&ctx); + + if (ngtcp2_crypto_derive_initial_secrets(version, rx_secret, tx_secret, + initial_secret, scid, + NGTCP2_CRYPTO_SIDE_SERVER) != 0) { + return -1; + } + + if (ngtcp2_crypto_derive_packet_protection_key( + tx_key, tx_iv, tx_hp_key, version, &ctx.aead, &ctx.md, tx_secret, + NGTCP2_CRYPTO_INITIAL_SECRETLEN) != 0) { + return -1; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &ctx.aead, tx_key, + NGTCP2_CRYPTO_INITIAL_IVLEN) != 0) { + spktlen = -1; + goto end; + } + + if (ngtcp2_crypto_cipher_ctx_encrypt_init(&hp_ctx, &ctx.hp, tx_hp_key) != 0) { + spktlen = -1; + goto end; + } + + spktlen = ngtcp2_pkt_write_connection_close( + dest, destlen, version, dcid, scid, error_code, reason, reasonlen, + ngtcp2_crypto_encrypt_cb, &ctx.aead, &aead_ctx, tx_iv, + ngtcp2_crypto_hp_mask_cb, &ctx.hp, &hp_ctx); + if (spktlen < 0) { + spktlen = -1; + } + +end: + ngtcp2_crypto_cipher_ctx_free(&hp_ctx); + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + return spktlen; +} + +ngtcp2_ssize ngtcp2_crypto_write_retry(uint8_t *dest, size_t destlen, + uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, + const ngtcp2_cid *odcid, + const uint8_t *token, size_t tokenlen) { + ngtcp2_crypto_aead aead; + ngtcp2_ssize spktlen; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + const uint8_t *key; + size_t noncelen; + + ngtcp2_crypto_aead_retry(&aead); + + switch (version) { + case NGTCP2_PROTO_VER_V1: + key = (const uint8_t *)NGTCP2_RETRY_KEY_V1; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2_DRAFT: + key = (const uint8_t *)NGTCP2_RETRY_KEY_V2_DRAFT; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V2_DRAFT) - 1; + break; + default: + key = (const uint8_t *)NGTCP2_RETRY_KEY_DRAFT; + noncelen = sizeof(NGTCP2_RETRY_NONCE_DRAFT) - 1; + } + + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key, noncelen) != + 0) { + return -1; + } + + spktlen = ngtcp2_pkt_write_retry(dest, destlen, version, dcid, scid, odcid, + token, tokenlen, ngtcp2_crypto_encrypt_cb, + &aead, &aead_ctx); + if (spktlen < 0) { + spktlen = -1; + } + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + return spktlen; +} + +int ngtcp2_crypto_client_initial_cb(ngtcp2_conn *conn, void *user_data) { + const ngtcp2_cid *dcid = ngtcp2_conn_get_dcid(conn); + void *tls = ngtcp2_conn_get_tls_native_handle(conn); + (void)user_data; + + if (ngtcp2_crypto_derive_and_install_initial_key( + conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + ngtcp2_conn_get_client_chosen_version(conn), dcid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (crypto_set_local_transport_params(conn, tls) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (ngtcp2_crypto_read_write_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_INITIAL, + NULL, 0) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int ngtcp2_crypto_recv_retry_cb(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + void *user_data) { + (void)user_data; + + if (ngtcp2_crypto_derive_and_install_initial_key( + conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + ngtcp2_conn_get_client_chosen_version(conn), &hd->scid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int ngtcp2_crypto_recv_client_initial_cb(ngtcp2_conn *conn, + const ngtcp2_cid *dcid, + void *user_data) { + (void)user_data; + + if (ngtcp2_crypto_derive_and_install_initial_key( + conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + ngtcp2_conn_get_client_chosen_version(conn), dcid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +int ngtcp2_crypto_version_negotiation_cb(ngtcp2_conn *conn, uint32_t version, + const ngtcp2_cid *client_dcid, + void *user_data) { + (void)user_data; + + if (ngtcp2_crypto_derive_and_install_vneg_initial_key( + conn, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, version, + client_dcid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +void ngtcp2_crypto_delete_crypto_aead_ctx_cb(ngtcp2_conn *conn, + ngtcp2_crypto_aead_ctx *aead_ctx, + void *user_data) { + (void)conn; + (void)user_data; + + ngtcp2_crypto_aead_ctx_free(aead_ctx); +} + +void ngtcp2_crypto_delete_crypto_cipher_ctx_cb( + ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data) { + (void)conn; + (void)user_data; + + ngtcp2_crypto_cipher_ctx_free(cipher_ctx); +} + +int ngtcp2_crypto_recv_crypto_data_cb(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, + size_t datalen, void *user_data) { + int rv; + (void)offset; + (void)user_data; + + if (ngtcp2_crypto_read_write_crypto_data(conn, crypto_level, data, datalen) != + 0) { + rv = ngtcp2_conn_get_tls_error(conn); + if (rv) { + return rv; + } + return NGTCP2_ERR_CRYPTO; + } + + return 0; +} diff --git a/crypto/shared.h b/crypto/shared.h new file mode 100644 index 0000000..02b9489 --- /dev/null +++ b/crypto/shared.h @@ -0,0 +1,350 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_SHARED_H +#define NGTCP2_SHARED_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2_crypto.h> + +/** + * @macro + * + * :macro:`NGTCP2_INITIAL_SALT_DRAFT` is a salt value which is used to + * derive initial secret. It is used for QUIC draft versions. + */ +#define NGTCP2_INITIAL_SALT_DRAFT \ + "\xaf\xbf\xec\x28\x99\x93\xd2\x4c\x9e\x97\x86\xf1\x9c\x61\x11\xe0\x43\x90" \ + "\xa8\x99" + +/** + * @macro + * + * :macro:`NGTCP2_INITIAL_SALT_V1` is a salt value which is used to + * derive initial secret. It is used for QUIC v1. + */ +#define NGTCP2_INITIAL_SALT_V1 \ + "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb" \ + "\x7f\x0a" + +/** + * @macro + * + * :macro:`NGTCP2_INITIAL_SALT_V2_DRAFT` is a salt value which is used to + * derive initial secret. It is used for QUIC v2 draft. + */ +#define NGTCP2_INITIAL_SALT_V2_DRAFT \ + "\xa7\x07\xc2\x03\xa5\x9b\x47\x18\x4a\x1d\x62\xca\x57\x04\x06\xea\x7a\xe3" \ + "\xe5\xd3" + +/* Maximum key usage (encryption) limits */ +#define NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM (1ULL << 23) +#define NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305 (1ULL << 62) +#define NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM (2965820ULL) + +/* Maximum authentication failure (decryption) limits during the + lifetime of a connection. */ +#define NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM (1ULL << 52) +#define NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305 (1ULL << 36) +#define NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM (2965820ULL) + +/** + * @function + * + * `ngtcp2_crypto_ctx_initial` initializes |ctx| for Initial packet + * encryption and decryption. + */ +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx); + +/** + * @function + * + * `ngtcp2_crypto_aead_init` initializes |aead| with the provided + * |aead_native_handle| which is an underlying AEAD object. + * + * If libngtcp2_crypto_openssl is linked, |aead_native_handle| must be + * a pointer to EVP_CIPHER. + * + * If libngtcp2_crypto_gnutls is linked, |aead_native_handle| must be + * gnutls_cipher_algorithm_t casted to ``void *``. + * + * If libngtcp2_crypto_boringssl is linked, |aead_native_handle| must + * be a pointer to EVP_AEAD. + */ +ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead, + void *aead_native_handle); + +/** + * @function + * + * `ngtcp2_crypto_aead_retry` initializes |aead| with the AEAD cipher + * AEAD_AES_128_GCM for Retry packet integrity protection. + */ +ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead); + +/** + * @function + * + * `ngtcp2_crypto_derive_initial_secrets` derives initial secrets. + * |rx_secret| and |tx_secret| must point to the buffer of at least 32 + * bytes capacity. rx for read and tx for write. This function + * writes rx and tx secrets into |rx_secret| and |tx_secret| + * respectively. The length of secret is 32 bytes long. + * |client_dcid| is the destination connection ID in first Initial + * packet of client. If |initial_secret| is not NULL, the initial + * secret is written to it. It must point to the buffer which has at + * least 32 bytes capacity. The initial secret is 32 bytes long. + * |side| specifies the side of application. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_derive_initial_secrets(uint32_t version, uint8_t *rx_secret, + uint8_t *tx_secret, + uint8_t *initial_secret, + const ngtcp2_cid *client_dcid, + ngtcp2_crypto_side side); + +/** + * @function + * + * `ngtcp2_crypto_derive_packet_protection_key` derives packet + * protection key. This function writes packet protection key into + * the buffer pointed by |key|. The length of derived key is + * `ngtcp2_crypto_aead_keylen(aead) <ngtcp2_crypto_aead_keylen>` + * bytes. |key| must have enough capacity to store the key. This + * function writes packet protection IV into |iv|. The length of + * derived IV is `ngtcp2_crypto_packet_protection_ivlen(aead) + * <ngtcp2_crypto_packet_protection_ivlen>` bytes. |iv| must have + * enough capacity to store the IV. + * + * If |hp| is not NULL, this function also derives packet header + * protection key and writes the key into the buffer pointed by |hp|. + * The length of derived key is `ngtcp2_crypto_aead_keylen(aead) + * <ngtcp2_crypto_aead_keylen>` bytes. |hp|, if not NULL, must have + * enough capacity to store the key. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_derive_packet_protection_key(uint8_t *key, uint8_t *iv, + uint8_t *hp, uint32_t version, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_md *md, + const uint8_t *secret, + size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_update_traffic_secret` derives the next generation + * of the traffic secret. |secret| specifies the current secret and + * its length is given in |secretlen|. The length of new key is the + * same as the current key. This function writes new key into the + * buffer pointed by |dest|. |dest| must have the enough capacity to + * store the new key. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_update_traffic_secret(uint8_t *dest, + const ngtcp2_crypto_md *md, + const uint8_t *secret, + size_t secretlen); + +/** + * @function + * + * `ngtcp2_crypto_set_local_transport_params` sets QUIC transport + * parameter, which is encoded in wire format and stored in the buffer + * pointed by |buf| of length |len|, to the native handle |tls|. + * + * |tls| points to a implementation dependent TLS session object. If + * libngtcp2_crypto_openssl is linked, |tls| must be a pointer to SSL + * object. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf, + size_t len); + +/** + * @function + * + * `ngtcp2_crypto_set_remote_transport_params` retrieves a remote QUIC + * transport parameters from |tls| and sets it to |conn| using + * `ngtcp2_conn_set_remote_transport_params`. + * + * |tls| points to a implementation dependent TLS session object. If + * libngtcp2_crypto_openssl is linked, |tls| must be a pointer to SSL + * object. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls); + +/** + * @function + * + * `ngtcp2_crypto_derive_and_install_initial_key` derives initial + * keying materials and installs keys to |conn|. + * + * If |rx_secret| is not NULL, the secret for decryption is written to + * the buffer pointed by |rx_secret|. The length of secret is 32 + * bytes, and |rx_secret| must point to the buffer which has enough + * capacity. + * + * If |tx_secret| is not NULL, the secret for encryption is written to + * the buffer pointed by |tx_secret|. The length of secret is 32 + * bytes, and |tx_secret| must point to the buffer which has enough + * capacity. + * + * If |initial_secret| is not NULL, the initial secret is written to + * the buffer pointed by |initial_secret|. The length of secret is 32 + * bytes, and |initial_secret| must point to the buffer which has + * enough capacity. + * + * |client_dcid| is the destination connection ID in first Initial + * packet of client. + * + * If |rx_key| is not NULL, the derived packet protection key for + * decryption is written to the buffer pointed by |rx_key|. If + * |rx_iv| is not NULL, the derived packet protection IV for + * decryption is written to the buffer pointed by |rx_iv|. If |rx_hp| + * is not NULL, the derived header protection key for decryption is + * written to the buffer pointed by |rx_hp|. + * + * If |tx_key| is not NULL, the derived packet protection key for + * encryption is written to the buffer pointed by |tx_key|. If + * |tx_iv| is not NULL, the derived packet protection IV for + * encryption is written to the buffer pointed by |tx_iv|. If |tx_hp| + * is not NULL, the derived header protection key for encryption is + * written to the buffer pointed by |tx_hp|. + * + * The length of packet protection key and header protection key is 16 + * bytes long. The length of packet protection IV is 12 bytes long. + * + * This function calls `ngtcp2_conn_set_initial_crypto_ctx` to set + * initial AEAD and message digest algorithm. After the successful + * call of this function, application can use + * `ngtcp2_conn_get_initial_crypto_ctx` to get the object. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_derive_and_install_initial_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, uint8_t *rx_hp, + uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp, uint32_t version, + const ngtcp2_cid *client_dcid); + +/** + * @function + * + * `ngtcp2_crypto_derive_and_install_vneg_initial_key` derives initial + * keying materials and installs keys to |conn|. This function is + * dedicated to install keys for |version| which is negotiated, or + * being negotiated. + * + * If |rx_secret| is not NULL, the secret for decryption is written to + * the buffer pointed by |rx_secret|. The length of secret is 32 + * bytes, and |rx_secret| must point to the buffer which has enough + * capacity. + * + * If |tx_secret| is not NULL, the secret for encryption is written to + * the buffer pointed by |tx_secret|. The length of secret is 32 + * bytes, and |tx_secret| must point to the buffer which has enough + * capacity. + * + * If |initial_secret| is not NULL, the initial secret is written to + * the buffer pointed by |initial_secret|. The length of secret is 32 + * bytes, and |initial_secret| must point to the buffer which has + * enough capacity. + * + * |client_dcid| is the destination connection ID in first Initial + * packet of client. + * + * If |rx_key| is not NULL, the derived packet protection key for + * decryption is written to the buffer pointed by |rx_key|. If + * |rx_iv| is not NULL, the derived packet protection IV for + * decryption is written to the buffer pointed by |rx_iv|. If |rx_hp| + * is not NULL, the derived header protection key for decryption is + * written to the buffer pointed by |rx_hp|. + * + * If |tx_key| is not NULL, the derived packet protection key for + * encryption is written to the buffer pointed by |tx_key|. If + * |tx_iv| is not NULL, the derived packet protection IV for + * encryption is written to the buffer pointed by |tx_iv|. If |tx_hp| + * is not NULL, the derived header protection key for encryption is + * written to the buffer pointed by |tx_hp|. + * + * The length of packet protection key and header protection key is 16 + * bytes long. The length of packet protection IV is 12 bytes long. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_derive_and_install_vneg_initial_key( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + uint8_t *initial_secret, uint8_t *rx_key, uint8_t *rx_iv, uint8_t *rx_hp, + uint8_t *tx_key, uint8_t *tx_iv, uint8_t *tx_hp, uint32_t version, + const ngtcp2_cid *client_dcid); + +/** + * @function + * + * `ngtcp2_crypto_cipher_ctx_encrypt_init` initializes |cipher_ctx| + * with new cipher context object for encryption which is constructed + * to use |key| as encryption key. |cipher| specifies cipher to use. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, + const ngtcp2_crypto_cipher *cipher, + const uint8_t *key); + +/** + * @function + * + * `ngtcp2_crypto_cipher_ctx_free` frees up resources used by + * |cipher_ctx|. This function does not free the memory pointed by + * |cipher_ctx| itself. + */ +void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx); + +/* + * `ngtcp2_crypto_md_sha256` initializes |md| with SHA256 message + * digest algorithm and returns |md|. + */ +ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md); + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead); + +/* + * `ngtcp2_crypto_random` writes cryptographically-secure random + * |datalen| bytes into the buffer pointed by |data|. + * + * This function returns 0 if it succeeds, or -1. + */ +int ngtcp2_crypto_random(uint8_t *data, size_t datalen); + +#endif /* NGTCP2_SHARED_H */ diff --git a/crypto/wolfssl/.gitignore b/crypto/wolfssl/.gitignore new file mode 100644 index 0000000..936b2be --- /dev/null +++ b/crypto/wolfssl/.gitignore @@ -0,0 +1 @@ +/libngtcp2_crypto_wolfssl.pc diff --git a/crypto/wolfssl/CMakeLists.txt b/crypto/wolfssl/CMakeLists.txt new file mode 100644 index 0000000..8cea3e5 --- /dev/null +++ b/crypto/wolfssl/CMakeLists.txt @@ -0,0 +1,83 @@ +# ngtcp2 + +# Copyright (c) 2022 ngtcp2 + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +add_definitions(-DBUILDING_NGTCP2) + +set(ngtcp2_crypto_wolfssl_SOURCES + wolfssl.c + ../shared.c +) + +set(ngtcp2_crypto_wolfssl_INCLUDE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../lib/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../lib" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto/includes" + "${CMAKE_CURRENT_SOURCE_DIR}/../../crypto" + "${CMAKE_CURRENT_BINARY_DIR}/../../crypto" + "${WOLFSSL_INCLUDE_DIRS}" +) + +foreach(name libngtcp2_crypto_wolfssl.pc) + configure_file("${name}.in" "${name}" @ONLY) +endforeach() + +# Public shared library +if(ENABLE_SHARED_LIB) + add_library(ngtcp2_crypto_wolfssl SHARED ${ngtcp2_crypto_wolfssl_SOURCES}) + set_target_properties(ngtcp2_crypto_wolfssl PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${CRYPTO_WOLFSSL_LT_VERSION} + SOVERSION ${CRYPTO_WOLFSSL_LT_SOVERSION} + C_VISIBILITY_PRESET hidden + POSITION_INDEPENDENT_CODE ON + ) + target_include_directories(ngtcp2_crypto_wolfssl PUBLIC + ${ngtcp2_crypto_wolfssl_INCLUDE_DIRS}) + target_link_libraries(ngtcp2_crypto_wolfssl ngtcp2 ${WOLFSSL_LIBRARIES}) + + install(TARGETS ngtcp2_crypto_wolfssl + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +endif() + +if(ENABLE_STATIC_LIB) + # Public static library + add_library(ngtcp2_crypto_wolfssl_static ${ngtcp2_crypto_wolfssl_SOURCES}) + set_target_properties(ngtcp2_crypto_wolfssl_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + C_VISIBILITY_PRESET hidden + ) + target_compile_definitions(ngtcp2_crypto_wolfssl_static PUBLIC + "-DNGTCP2_STATICLIB") + target_include_directories(ngtcp2_crypto_wolfssl_static PUBLIC + ${ngtcp2_crypto_wolfssl_INCLUDE_DIRS}) + + install(TARGETS ngtcp2_crypto_wolfssl_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") +endif() + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2_crypto_wolfssl.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/crypto/wolfssl/Makefile.am b/crypto/wolfssl/Makefile.am new file mode 100644 index 0000000..b9d5f8c --- /dev/null +++ b/crypto/wolfssl/Makefile.am @@ -0,0 +1,43 @@ +# ngtcp2 + +# Copyright (c) 2022 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG) +AM_CPPFLAGS = -I$(top_srcdir)/lib/includes -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/lib -DBUILDING_NGTCP2 \ + -I$(top_srcdir)/crypto/includes -I$(top_builddir)/crypto/includes \ + -I$(top_srcdir)/crypto -I$(top_builddir)/crypto \ + @WOLFSSL_CFLAGS@ +AM_LDFLAGS = ${LIBTOOL_LDFLAGS} + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libngtcp2_crypto_wolfssl.pc +DISTCLEANFILES = $(pkgconfig_DATA) + +lib_LTLIBRARIES = libngtcp2_crypto_wolfssl.la + +libngtcp2_crypto_wolfssl_la_SOURCES = wolfssl.c ../shared.c ../shared.h +libngtcp2_crypto_wolfssl_la_LDFLAGS = -no-undefined \ + -version-info $(CRYPTO_WOLFSSL_LT_CURRENT):$(CRYPTO_WOLFSSL_LT_REVISION):$(CRYPTO_WOLFSSL_LT_AGE) +libngtcp2_crypto_wolfssl_la_LIBADD = $(top_builddir)/lib/libngtcp2.la \ + @WOLFSSL_LIBS@ diff --git a/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in b/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in new file mode 100644 index 0000000..720c784 --- /dev/null +++ b/crypto/wolfssl/libngtcp2_crypto_wolfssl.pc.in @@ -0,0 +1,33 @@ +# ngtcp2 + +# Copyright (c) 2022 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libngtcp2_crypto_wolfssl +Description: ngtcp2 wolfSSL crypto library +URL: https://github.com/ngtcp2/ngtcp2 +Version: @VERSION@ +Libs: -L${libdir} -lngtcp2_crypto_wolfssl +Cflags: -I${includedir} diff --git a/crypto/wolfssl/wolfssl.c b/crypto/wolfssl/wolfssl.c new file mode 100644 index 0000000..4c341de --- /dev/null +++ b/crypto/wolfssl/wolfssl.c @@ -0,0 +1,534 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <assert.h> + +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_wolfssl.h> + +#include <wolfssl/ssl.h> +#include <wolfssl/quic.h> + +#include "shared.h" + +#define PRINTF_DEBUG 0 +#if PRINTF_DEBUG +# define DEBUG_MSG(...) fprintf(stderr, __VA_ARGS__) +#else +# define DEBUG_MSG(...) (void)0 +#endif + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_aes_128_gcm(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)wolfSSL_EVP_aes_128_gcm()); +} + +ngtcp2_crypto_md *ngtcp2_crypto_md_sha256(ngtcp2_crypto_md *md) { + md->native_handle = (void *)wolfSSL_EVP_sha256(); + return md; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_initial(ngtcp2_crypto_ctx *ctx) { + ngtcp2_crypto_aead_init(&ctx->aead, (void *)wolfSSL_EVP_aes_128_gcm()); + ctx->md.native_handle = (void *)wolfSSL_EVP_sha256(); + ctx->hp.native_handle = (void *)wolfSSL_EVP_aes_128_ctr(); + ctx->max_encryption = 0; + ctx->max_decryption_failure = 0; + return ctx; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_init(ngtcp2_crypto_aead *aead, + void *aead_native_handle) { + aead->native_handle = aead_native_handle; + aead->max_overhead = wolfSSL_quic_get_aead_tag_len( + (const WOLFSSL_EVP_CIPHER *)(aead_native_handle)); + return aead; +} + +ngtcp2_crypto_aead *ngtcp2_crypto_aead_retry(ngtcp2_crypto_aead *aead) { + return ngtcp2_crypto_aead_init(aead, (void *)wolfSSL_EVP_aes_128_gcm()); +} + +static uint64_t crypto_wolfssl_get_aead_max_encryption(WOLFSSL *ssl) { + const WOLFSSL_EVP_CIPHER *aead = wolfSSL_quic_get_aead(ssl); + + if (wolfSSL_quic_aead_is_gcm(aead)) { + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_GCM; + } + if (wolfSSL_quic_aead_is_chacha20(aead)) { + return NGTCP2_CRYPTO_MAX_ENCRYPTION_CHACHA20_POLY1305; + } + if (wolfSSL_quic_aead_is_ccm(aead)) { + return NGTCP2_CRYPTO_MAX_ENCRYPTION_AES_CCM; + } + return 0; +} + +static uint64_t crypto_wolfssl_get_aead_max_decryption_failure(WOLFSSL *ssl) { + const WOLFSSL_EVP_CIPHER *aead = wolfSSL_quic_get_aead(ssl); + + if (wolfSSL_quic_aead_is_gcm(aead)) { + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_GCM; + } + if (wolfSSL_quic_aead_is_chacha20(aead)) { + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_CHACHA20_POLY1305; + } + if (wolfSSL_quic_aead_is_ccm(aead)) { + return NGTCP2_CRYPTO_MAX_DECRYPTION_FAILURE_AES_CCM; + } + return 0; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + WOLFSSL *ssl = tls_native_handle; + + ngtcp2_crypto_aead_init(&ctx->aead, (void *)wolfSSL_quic_get_aead(ssl)); + ctx->md.native_handle = (void *)wolfSSL_quic_get_md(ssl); + ctx->hp.native_handle = (void *)wolfSSL_quic_get_hp(ssl); + ctx->max_encryption = crypto_wolfssl_get_aead_max_encryption(ssl); + ctx->max_decryption_failure = + crypto_wolfssl_get_aead_max_decryption_failure(ssl); + return ctx; +} + +ngtcp2_crypto_ctx *ngtcp2_crypto_ctx_tls_early(ngtcp2_crypto_ctx *ctx, + void *tls_native_handle) { + return ngtcp2_crypto_ctx_tls(ctx, tls_native_handle); +} + +static size_t crypto_md_hashlen(const WOLFSSL_EVP_MD *md) { + return (size_t)wolfSSL_EVP_MD_size(md); +} + +size_t ngtcp2_crypto_md_hashlen(const ngtcp2_crypto_md *md) { + return crypto_md_hashlen(md->native_handle); +} + +static size_t crypto_aead_keylen(const WOLFSSL_EVP_CIPHER *aead) { + return (size_t)wolfSSL_EVP_Cipher_key_length(aead); +} + +size_t ngtcp2_crypto_aead_keylen(const ngtcp2_crypto_aead *aead) { + return crypto_aead_keylen(aead->native_handle); +} + +static size_t crypto_aead_noncelen(const WOLFSSL_EVP_CIPHER *aead) { + return (size_t)wolfSSL_EVP_CIPHER_iv_length(aead); +} + +size_t ngtcp2_crypto_aead_noncelen(const ngtcp2_crypto_aead *aead) { + return crypto_aead_noncelen(aead->native_handle); +} + +int ngtcp2_crypto_aead_ctx_encrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + const WOLFSSL_EVP_CIPHER *cipher = aead->native_handle; + WOLFSSL_EVP_CIPHER_CTX *actx; + static const uint8_t iv[AES_BLOCK_SIZE] = {0}; + + (void)noncelen; + actx = wolfSSL_quic_crypt_new(cipher, key, iv, /* encrypt */ 1); + if (actx == NULL) { + return -1; + } + + aead_ctx->native_handle = actx; + return 0; +} + +int ngtcp2_crypto_aead_ctx_decrypt_init(ngtcp2_crypto_aead_ctx *aead_ctx, + const ngtcp2_crypto_aead *aead, + const uint8_t *key, size_t noncelen) { + const WOLFSSL_EVP_CIPHER *cipher = aead->native_handle; + WOLFSSL_EVP_CIPHER_CTX *actx; + static const uint8_t iv[AES_BLOCK_SIZE] = {0}; + + (void)noncelen; + actx = wolfSSL_quic_crypt_new(cipher, key, iv, /* encrypt */ 0); + if (actx == NULL) { + return -1; + } + + aead_ctx->native_handle = actx; + return 0; +} + +void ngtcp2_crypto_aead_ctx_free(ngtcp2_crypto_aead_ctx *aead_ctx) { + if (aead_ctx->native_handle) { + wolfSSL_EVP_CIPHER_CTX_free(aead_ctx->native_handle); + } +} + +int ngtcp2_crypto_cipher_ctx_encrypt_init(ngtcp2_crypto_cipher_ctx *cipher_ctx, + const ngtcp2_crypto_cipher *cipher, + const uint8_t *key) { + WOLFSSL_EVP_CIPHER_CTX *actx; + + actx = + wolfSSL_quic_crypt_new(cipher->native_handle, key, NULL, /* encrypt */ 1); + if (actx == NULL) { + return -1; + } + + cipher_ctx->native_handle = actx; + return 0; +} + +void ngtcp2_crypto_cipher_ctx_free(ngtcp2_crypto_cipher_ctx *cipher_ctx) { + if (cipher_ctx->native_handle) { + wolfSSL_EVP_CIPHER_CTX_free(cipher_ctx->native_handle); + } +} + +int ngtcp2_crypto_hkdf_extract(uint8_t *dest, const ngtcp2_crypto_md *md, + const uint8_t *secret, size_t secretlen, + const uint8_t *salt, size_t saltlen) { + if (wolfSSL_quic_hkdf_extract(dest, md->native_handle, secret, secretlen, + salt, saltlen) != WOLFSSL_SUCCESS) { + DEBUG_MSG("WOLFSSL: wolfSSL_quic_hkdf_extract FAILED\n"); + return -1; + } + return 0; +} + +int ngtcp2_crypto_hkdf_expand(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *info, + size_t infolen) { + if (wolfSSL_quic_hkdf_expand(dest, destlen, md->native_handle, secret, + secretlen, info, infolen) != WOLFSSL_SUCCESS) { + DEBUG_MSG("WOLFSSL: wolfSSL_quic_hkdf_expand FAILED\n"); + return -1; + } + return 0; +} + +int ngtcp2_crypto_hkdf(uint8_t *dest, size_t destlen, + const ngtcp2_crypto_md *md, const uint8_t *secret, + size_t secretlen, const uint8_t *salt, size_t saltlen, + const uint8_t *info, size_t infolen) { + if (wolfSSL_quic_hkdf(dest, destlen, md->native_handle, secret, secretlen, + salt, saltlen, info, infolen) != WOLFSSL_SUCCESS) { + DEBUG_MSG("WOLFSSL: wolfSSL_quic_hkdf FAILED\n"); + return -1; + } + return 0; +} + +int ngtcp2_crypto_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + (void)aead; + (void)noncelen; + if (wolfSSL_quic_aead_encrypt(dest, aead_ctx->native_handle, plaintext, + plaintextlen, nonce, aad, + aadlen) != WOLFSSL_SUCCESS) { + DEBUG_MSG("WOLFSSL: encrypt FAILED\n"); + return -1; + } + return 0; +} + +int ngtcp2_crypto_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + (void)aead; + (void)noncelen; + if (wolfSSL_quic_aead_decrypt(dest, aead_ctx->native_handle, ciphertext, + ciphertextlen, nonce, aad, + aadlen) != WOLFSSL_SUCCESS) { + + DEBUG_MSG("WOLFSSL: decrypt FAILED\n"); + return -1; + } + return 0; +} + +int ngtcp2_crypto_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + static const uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00"; + WOLFSSL_EVP_CIPHER_CTX *actx = hp_ctx->native_handle; + int len; + + (void)hp; + + if (wolfSSL_EVP_EncryptInit_ex(actx, NULL, NULL, NULL, sample) != + WOLFSSL_SUCCESS || + wolfSSL_EVP_CipherUpdate(actx, dest, &len, PLAINTEXT, + sizeof(PLAINTEXT) - 1) != WOLFSSL_SUCCESS || + wolfSSL_EVP_EncryptFinal_ex(actx, dest + sizeof(PLAINTEXT) - 1, &len) != + WOLFSSL_SUCCESS) { + DEBUG_MSG("WOLFSSL: hp_mask FAILED\n"); + return -1; + } + + return 0; +} + +int ngtcp2_crypto_read_write_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + const uint8_t *data, size_t datalen) { + WOLFSSL *ssl = ngtcp2_conn_get_tls_native_handle(conn); + WOLFSSL_ENCRYPTION_LEVEL level = + ngtcp2_crypto_wolfssl_from_ngtcp2_crypto_level(crypto_level); + int rv; + int err; + + DEBUG_MSG("WOLFSSL: read/write crypto data, level=%d len=%lu\n", level, + datalen); + if (datalen > 0) { + rv = wolfSSL_provide_quic_data(ssl, level, data, datalen); + if (rv != WOLFSSL_SUCCESS) { + DEBUG_MSG("WOLFSSL: read/write crypto data FAILED, rv=%d\n", rv); + return -1; + } + } + + if (!ngtcp2_conn_get_handshake_completed(conn)) { + rv = wolfSSL_quic_do_handshake(ssl); + if (rv <= 0) { + err = wolfSSL_get_error(ssl, rv); + DEBUG_MSG("WOLFSSL: do_handshake, rv=%d, err=%d\n", rv, err); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_SSL: + return -1; + default: + return -1; + } + } + + DEBUG_MSG("WOLFSSL: handshake done\n"); + ngtcp2_conn_handshake_completed(conn); + } + + rv = wolfSSL_process_quic_post_handshake(ssl); + DEBUG_MSG("WOLFSSL: process post handshake, rv=%d\n", rv); + if (rv != 1) { + err = wolfSSL_get_error(ssl, rv); + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_SSL: + case SSL_ERROR_ZERO_RETURN: + return -1; + default: + return -1; + } + } + + return 0; +} + +int ngtcp2_crypto_set_remote_transport_params(ngtcp2_conn *conn, void *tls) { + WOLFSSL *ssl = tls; + const uint8_t *tp; + size_t tplen; + int rv; + + wolfSSL_get_peer_quic_transport_params(ssl, &tp, &tplen); + DEBUG_MSG("WOLFSSL: get peer transport params, len=%lu\n", tplen); + + rv = ngtcp2_conn_decode_remote_transport_params(conn, tp, tplen); + if (rv != 0) { + DEBUG_MSG("WOLFSSL: decode peer transport params failed, rv=%d\n", rv); + ngtcp2_conn_set_tls_error(conn, rv); + return -1; + } + + return 0; +} + +int ngtcp2_crypto_set_local_transport_params(void *tls, const uint8_t *buf, + size_t len) { + WOLFSSL *ssl = tls; + DEBUG_MSG("WOLFSSL: set local peer transport params, len=%lu\n", len); + if (wolfSSL_set_quic_transport_params(ssl, buf, len) != WOLFSSL_SUCCESS) { + return -1; + } + + return 0; +} + +ngtcp2_crypto_level ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level( + WOLFSSL_ENCRYPTION_LEVEL wolfssl_level) { + switch (wolfssl_level) { + case wolfssl_encryption_initial: + return NGTCP2_CRYPTO_LEVEL_INITIAL; + case wolfssl_encryption_early_data: + return NGTCP2_CRYPTO_LEVEL_EARLY; + case wolfssl_encryption_handshake: + return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + case wolfssl_encryption_application: + return NGTCP2_CRYPTO_LEVEL_APPLICATION; + default: + assert(0); + abort(); /* if NDEBUG is set */ + } +} + +WOLFSSL_ENCRYPTION_LEVEL +ngtcp2_crypto_wolfssl_from_ngtcp2_crypto_level( + ngtcp2_crypto_level crypto_level) { + switch (crypto_level) { + case NGTCP2_CRYPTO_LEVEL_INITIAL: + return wolfssl_encryption_initial; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + return wolfssl_encryption_handshake; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + return wolfssl_encryption_application; + case NGTCP2_CRYPTO_LEVEL_EARLY: + return wolfssl_encryption_early_data; + default: + assert(0); + abort(); /* if NDEBUG is set */ + } +} + +int ngtcp2_crypto_get_path_challenge_data_cb(ngtcp2_conn *conn, uint8_t *data, + void *user_data) { + (void)conn; + (void)user_data; + + DEBUG_MSG("WOLFSSL: get path challenge data\n"); + if (wolfSSL_RAND_bytes(data, NGTCP2_PATH_CHALLENGE_DATALEN) != 1) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} + +int ngtcp2_crypto_random(uint8_t *data, size_t datalen) { + DEBUG_MSG("WOLFSSL: get random\n"); + if (wolfSSL_RAND_bytes(data, (int)datalen) != 1) { + return -1; + } + return 0; +} + +static int set_encryption_secrets(WOLFSSL *ssl, + WOLFSSL_ENCRYPTION_LEVEL wolfssl_level, + const uint8_t *rx_secret, + const uint8_t *tx_secret, size_t secretlen) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = + ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level(wolfssl_level); + + DEBUG_MSG("WOLFSSL: set encryption secrets, level=%d, rxlen=%lu, txlen=%lu\n", + wolfssl_level, rx_secret ? secretlen : 0, + tx_secret ? secretlen : 0); + if (rx_secret && + ngtcp2_crypto_derive_and_install_rx_key(conn, NULL, NULL, NULL, level, + rx_secret, secretlen) != 0) { + return 0; + } + + if (tx_secret && + ngtcp2_crypto_derive_and_install_tx_key(conn, NULL, NULL, NULL, level, + tx_secret, secretlen) != 0) { + return 0; + } + + return 1; +} + +static int add_handshake_data(WOLFSSL *ssl, + WOLFSSL_ENCRYPTION_LEVEL wolfssl_level, + const uint8_t *data, size_t datalen) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + ngtcp2_crypto_level level = + ngtcp2_crypto_wolfssl_from_wolfssl_encryption_level(wolfssl_level); + int rv; + + DEBUG_MSG("WOLFSSL: add handshake data, level=%d len=%lu\n", wolfssl_level, + datalen); + rv = ngtcp2_conn_submit_crypto_data(conn, level, data, datalen); + if (rv != 0) { + ngtcp2_conn_set_tls_error(conn, rv); + return 0; + } + + return 1; +} + +static int flush_flight(WOLFSSL *ssl) { + (void)ssl; + return 1; +} + +static int send_alert(WOLFSSL *ssl, enum wolfssl_encryption_level_t level, + uint8_t alert) { + ngtcp2_crypto_conn_ref *conn_ref = SSL_get_app_data(ssl); + ngtcp2_conn *conn = conn_ref->get_conn(conn_ref); + (void)level; + + DEBUG_MSG("WOLFSSL: send alert, level=%d alert=%d\n", level, alert); + ngtcp2_conn_set_tls_alert(conn, alert); + + return 1; +} + +static WOLFSSL_QUIC_METHOD quic_method = { + set_encryption_secrets, + add_handshake_data, + flush_flight, + send_alert, +}; + +static void crypto_wolfssl_configure_context(WOLFSSL_CTX *ssl_ctx) { + wolfSSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION); + wolfSSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION); + wolfSSL_CTX_set_quic_method(ssl_ctx, &quic_method); +} + +int ngtcp2_crypto_wolfssl_configure_server_context(WOLFSSL_CTX *ssl_ctx) { + crypto_wolfssl_configure_context(ssl_ctx); +#if PRINTF_DEBUG + wolfSSL_Debugging_ON(); +#endif + return 0; +} + +int ngtcp2_crypto_wolfssl_configure_client_context(WOLFSSL_CTX *ssl_ctx) { + crypto_wolfssl_configure_context(ssl_ctx); + wolfSSL_CTX_UseSessionTicket(ssl_ctx); +#if PRINTF_DEBUG + wolfSSL_Debugging_ON(); +#endif + return 0; +} 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..e2339bd --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,65 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# 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 cryptoapiref + +apiref: mkapiref.py \ + $(top_builddir)/lib/includes/ngtcp2/version.h \ + $(top_srcdir)/lib/includes/ngtcp2/ngtcp2.h + $(top_srcdir)/doc/mkapiref.py \ + --title='ngtcp2 API reference' \ + source/apiref.rst source/macros.rst source/enums.rst source/types.rst \ + source $(wordlist 2, $(words $^), $^) + +cryptoapiref: mkapiref.py \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_openssl.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_gnutls.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_boringssl.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_picotls.h \ + $(top_srcdir)/crypto/includes/ngtcp2/ngtcp2_crypto_wolfssl.h + $(top_srcdir)/doc/mkapiref.py \ + --title='ngtcp2 crypto API reference' \ + source/crypto_apiref.rst source/crypto_macros.rst \ + source/crypto_enums.rst source/crypto_types.rst \ + source $(wordlist 2, $(words $^), $^) + +html-local: apiref cryptoapiref + @$(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..2119f51 --- /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=.
+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..a0d0483 --- /dev/null +++ b/doc/mkapiref.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# nghttp2 - HTTP/2 C Library +# +# 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'(ngtcp2_[^ )]+)\(', 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 <ngtcp2/{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 (pkt_info|transport_params|conn_stat|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'NGTCP2_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..a03e54b --- /dev/null +++ b/doc/source/.gitignore @@ -0,0 +1,9 @@ +/conf.py +/apiref.rst +/enums.rst +/macros.rst +/types.rst +/crypto_apiref.rst +/crypto_enums.rst +/crypto_macros.rst +/crypto_types.rst diff --git a/doc/source/conf.py.in b/doc/source/conf.py.in new file mode 100644 index 0000000..6ac325c --- /dev/null +++ b/doc/source/conf.py.in @@ -0,0 +1,95 @@ +# ngtcp2 + +# Copyright (c) 2020 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# 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 = 'ngtcp2' +copyright = '2020, ngtcp2 contributors' +author = 'ngtcp2 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 = 'sphinx_rtd_theme' +html_theme_path = ['_themes'] + +# 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..6890563 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,22 @@ +.. ngtcp2 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 ngtcp2's documentation! +================================== + +.. toctree:: + :maxdepth: 1 + :caption: Contents: + + programmers-guide + apiref + crypto_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..5138361 --- /dev/null +++ b/doc/source/programmers-guide.rst @@ -0,0 +1,446 @@ +The ngtcp2 programmers' guide for early adopters +================================================ + +This document is written for early adopters of ngtcp2 library. It +describes a brief introduction of programming ngtcp2. + +Prerequisites +------------- + +Reading :rfc:`9000` and :rfc:`9001` helps you a lot to write QUIC +application. They describes how TLS is integrated into QUIC and why +the existing TLS stack cannot be used with QUIC. + +QUIC requires the special interface from TLS stack, which is probably +not available from most of the existing TLS stacks. As far as I know, +the TLS stacks maintained by the active participants of QUIC working +group only get this interface at the time of this writing. In order +to build QUIC application you have to choose one of them. Here is the +list of TLS stacks which are supposed to provide such interface and +for which we provide crypto helper libraries: + +* `OpenSSL with QUIC support + <https://github.com/quictls/openssl/tree/OpenSSL_1_1_1q+quic>`_ +* GnuTLS >= 3.7.2 +* BoringSSL +* Picotls +* wolfSSL + +Creating ngtcp2_conn object +--------------------------- + +:type:`ngtcp2_conn` is the primary object to present a single QUIC +connection. Use `ngtcp2_conn_client_new()` for client application, +and `ngtcp2_conn_server_new()` for server. + +They require :type:`ngtcp2_callbacks`, :type:`ngtcp2_settings`, and +:type:`ngtcp2_transport_params` objects. + +The :type:`ngtcp2_callbacks` contains the callback functions which +:type:`ngtcp2_conn` calls when a specific event happens, say, +receiving stream data or stream is closed, etc. Some of the callback +functions are optional. For client application, the following +callback functions must be set: + +* :member:`client_initial <ngtcp2_callbacks.client_initial>`: + `ngtcp2_crypto_client_initial_cb()` can be passed directly. +* :member:`recv_crypto_data <ngtcp2_callbacks.recv_crypto_data>`: + `ngtcp2_crypto_recv_crypto_data_cb()` can be passed directly. +* :member:`encrypt <ngtcp2_callbacks.encrypt>`: + `ngtcp2_crypto_encrypt_cb()` can be passed directly. +* :member:`decrypt <ngtcp2_callbacks.decrypt>`: + `ngtcp2_crypto_decrypt_cb()` can be passed directly. +* :member:`hp_mask <ngtcp2_callbacks.hp_mask>`: + `ngtcp2_crypto_hp_mask_cb()` can be passed directly. +* :member:`recv_retry <ngtcp2_callbacks.recv_retry>`: + `ngtcp2_crypto_recv_retry_cb()` can be passed directly. +* :member:`rand <ngtcp2_callbacks.rand>` +* :member:`get_new_connection_id + <ngtcp2_callbacks.get_new_connection_id>` +* :member:`update_key <ngtcp2_callbacks.update_key>`: + `ngtcp2_crypto_update_key_cb()` can be passed directly. +* :member:`delete_crypto_aead_ctx + <ngtcp2_callbacks.delete_crypto_aead_ctx>`: + `ngtcp2_crypto_delete_crypto_aead_ctx_cb()` can be passed directly. +* :member:`delete_crypto_cipher_ctx + <ngtcp2_callbacks.delete_crypto_cipher_ctx>`: + `ngtcp2_crypto_delete_crypto_cipher_ctx_cb()` can be passed + directly. +* :member:`get_path_challenge_data + <ngtcp2_callbacks.get_path_challenge_data>`: + `ngtcp2_crypto_get_path_challenge_data_cb()` can be passed directly. +* :member:`version_negotiation + <ngtcp2_callbacks.version_negotiation>`: + `ngtcp2_crypto_version_negotiation_cb()` can be passed directly. + +For server application, the following callback functions must be set: + +* :member:`recv_client_initial + <ngtcp2_callbacks.recv_client_initial>`: + `ngtcp2_crypto_recv_client_initial_cb()` can be passed directly. +* :member:`recv_crypto_data <ngtcp2_callbacks.recv_crypto_data>`: + `ngtcp2_crypto_recv_crypto_data_cb()` can be passed directly. +* :member:`encrypt <ngtcp2_callbacks.encrypt>`: + `ngtcp2_crypto_encrypt_cb()` can be passed directly. +* :member:`decrypt <ngtcp2_callbacks.decrypt>`: + `ngtcp2_crypto_decrypt_cb()` can be passed directly. +* :member:`hp_mask <ngtcp2_callbacks.hp_mask>`: + `ngtcp2_crypto_hp_mask_cb()` can be passed directly. +* :member:`rand <ngtcp2_callbacks.rand>` +* :member:`get_new_connection_id + <ngtcp2_callbacks.get_new_connection_id>` +* :member:`update_key <ngtcp2_callbacks.update_key>`: + `ngtcp2_crypto_update_key_cb()` can be passed directly. +* :member:`delete_crypto_aead_ctx + <ngtcp2_callbacks.delete_crypto_aead_ctx>`: + `ngtcp2_crypto_delete_crypto_aead_ctx_cb()` can be passed directly. +* :member:`delete_crypto_cipher_ctx + <ngtcp2_callbacks.delete_crypto_cipher_ctx>`: + `ngtcp2_crypto_delete_crypto_cipher_ctx_cb()` can be passed + directly. +* :member:`get_path_challenge_data + <ngtcp2_callbacks.get_path_challenge_data>`: + `ngtcp2_crypto_get_path_challenge_data_cb()` can be passed directly. +* :member:`version_negotiation + <ngtcp2_callbacks.version_negotiation>`: + `ngtcp2_crypto_version_negotiation_cb()` can be passed directly. + +``ngtcp2_crypto_*`` functions are a part of :doc:`ngtcp2 crypto API +<crypto_apiref>` which provides easy integration with the supported +TLS backend. It vastly simplifies TLS integration and is strongly +recommended. + +:type:`ngtcp2_settings` contains the settings for QUIC connection. +All fields must be set. Application should call +`ngtcp2_settings_default()` to set the default values. It would be +very useful to enable debug logging by setting logging function to +:member:`ngtcp2_settings.log_printf` field. ngtcp2 library relies on +the timestamp fed from application. The initial timestamp must be +passed to :member:`ngtcp2_settings.initial_ts` field in nanosecond +resolution. ngtcp2 cares about the difference from that initial +value. It could be any timestamp which increases monotonically, and +actual value does not matter. + +:type:`ngtcp2_transport_params` contains QUIC transport parameters +which is sent to a remote endpoint during handshake. All fields must +be set. Application should call `ngtcp2_transport_params_default()` +to set the default values. + +Client application has to supply Connection IDs to +`ngtcp2_conn_client_new()`. The *dcid* parameter is the destination +connection ID (DCID), and which should be random byte string and at +least 8 bytes long. The *scid* is the source connection ID (SCID) +which identifies the client itself. The *version* parameter is the +QUIC version to use. It should be :macro:`NGTCP2_PROTO_VER_V1`. + +Similarly, server application has to supply these parameters to +`ngtcp2_conn_server_new()`. But the *dcid* must be the same value +which is received from client (which is client SCID). The *scid* is +chosen by server. Don't use DCID in client packet as server SCID. +The *version* parameter is the QUIC version to use. It should be +:macro:`NGTCP2_PROTO_VER_V1`. + +A path is very important to QUIC connection. It is the pair of +endpoints, local and remote. The path passed to +`ngtcp2_conn_client_new()` and `ngtcp2_conn_server_new()` is a network +path that handshake is performed. The path must not change during +handshake. After handshake is confirmed, client can migrate to new +path. An application must provide actual path to the API function to +tell the library where a packet comes from. The "write" API function +takes path parameter and fills it to which the packet should be sent. + +TLS integration +--------------- + +Use of :doc:`ngtcp2 crypto API <crypto_apiref>` is strongly +recommended because it vastly simplifies the TLS integration. + +The most of the TLS work is done by the callback functions passed to +:type:`ngtcp2_callbacks` object. There are some operations left to +application in order to make TLS integration work. We have a set of +helper functions to make it easier for applications to configure TLS +stack object to work with QUIC and ngtcp2. They are specific to each +supported TLS stack: + +- OpenSSL + + * `ngtcp2_crypto_openssl_configure_client_context` + * `ngtcp2_crypto_openssl_configure_server_context` + +- BoringSSL + + * `ngtcp2_crypto_boringssl_configure_client_context` + * `ngtcp2_crypto_boringssl_configure_server_context` + +- GnuTLS + + * `ngtcp2_crypto_gnutls_configure_client_session` + * `ngtcp2_crypto_gnutls_configure_server_session` + +- Picotls + + * `ngtcp2_crypto_picotls_configure_client_context` + * `ngtcp2_crypto_picotls_configure_server_context` + * `ngtcp2_crypto_picotls_configure_client_session` + * `ngtcp2_crypto_picotls_configure_server_session` + +- wolfSSL + + * `ngtcp2_crypto_wolfssl_configure_client_context` + * `ngtcp2_crypto_wolfssl_configure_server_context` + +They make the minimal QUIC specific changes to TLS stack object. See +the ngtcp2 crypto API header files for each supported TLS stack. In +order to make these functions work, we require that a pointer to +:type:`ngtcp2_crypto_conn_ref` must be set as a user data in TLS stack +object, and its :member:`ngtcp2_crypto_conn_ref.get_conn` must point +to a function which returns :type:`ngtcp2_conn` of the underlying QUIC +connection. + +If you do not use the above helper functions, you need to generate and +install keys to :type:`ngtcp2_conn`, and pass handshake messages to +:type:`ngtcp2_conn` as well. When TLS stack generates new secrets, +they have to be installed to :type:`ngtcp2_conn` by calling +`ngtcp2_crypto_derive_and_install_rx_key()` and +`ngtcp2_crypto_derive_and_install_tx_key()`. When TLS stack generates +new crypto data to send, they must be passed to :type:`ngtcp2_conn` by +calling `ngtcp2_conn_submit_crypto_data()`. + +When QUIC handshake is completed, +:member:`ngtcp2_callbacks.handshake_completed` callback function is +called. The local and remote endpoint independently declare handshake +completion. The endpoint has to confirm that the other endpoint also +finished handshake. When the handshake is confirmed, client side +:type:`ngtcp2_conn` will call +:member:`ngtcp2_callbacks.handshake_confirmed` callback function. +Server confirms handshake when it declares handshake completion, +therefore, separate handshake confirmation callback is not called. + +Read and write packets +---------------------- + +`ngtcp2_conn_read_pkt()` processes the incoming QUIC packets. In +order to write QUIC packets, call `ngtcp2_conn_writev_stream()` or +`ngtcp2_conn_write_pkt()`. The *destlen* parameter must be at least +the value returned from `ngtcp2_conn_get_max_tx_udp_payload_size()`. + +In order to send stream data, the application has to first open a +stream. Use `ngtcp2_conn_open_bidi_stream()` to open bidirectional +stream. For unidirectional stream, call +`ngtcp2_conn_open_uni_stream()`. Call `ngtcp2_conn_writev_stream()` +to send stream data. + +An application should pace sending packets. +`ngtcp2_conn_get_send_quantum()` returns the number of bytes that can +be sent without packet spacing. After one or more calls of +`ngtcp2_conn_writev_stream()` (it can be called multiple times to fill +the buffer sized up to `ngtcp2_conn_get_send_quantum()` bytes), call +`ngtcp2_conn_update_pkt_tx_time()` to set the timer when the next +packet should be sent. The timer is integrated into +`ngtcp2_conn_get_expiry()`. + +Packet handling on server side +------------------------------ + +Any incoming UDP datagram should be first processed by +`ngtcp2_pkt_decode_version_cid()`. It can handle Connection ID more +than 20 bytes which is the maximum length defined in QUIC v1. If the +function returns :macro:`NGTCP2_ERR_VERSION_NEGOTIATION`, server +should send Version Negotiation packet. Use +`ngtcp2_pkt_write_version_negotiation()` for this purpose. If +`ngtcp2_pkt_decode_version_cid()` succeeds, then check whether the UDP +datagram belongs to any existing connection by looking up connection +tables by Destination Connection ID. If it belongs to an existing +connection, pass the UDP datagram to `ngtcp2_conn_read_pkt()`. If it +does not belong to any existing connection, it should be passed to +`ngtcp2_accept()`. If it returns :macro:`NGTCP2_ERR_RETRY`, the +server should send Retry packet (use `ngtcp2_crypto_write_retry()` to +create Retry packet). If it returns an other negative error code, +just drop the packet to the floor and take no action, or send +Stateless Reset packet (use `ngtcp2_pkt_write_stateless_reset()` to +create Stateless Reset packet). Otherwise, the UDP datagram is +acceptable as a new connection. Create :type:`ngtcp2_conn` object and +pass the UDP datagram to `ngtcp2_conn_read_pkt()`. + +Dealing with early data +----------------------- + +Client application has to load resumed TLS session. It also has to +set the remembered transport parameters using +`ngtcp2_conn_set_early_remote_transport_params()` function. + +Other than that, there is no difference between early data and 1RTT +data in terms of API usage. + +If early data is rejected by a server, client must call +`ngtcp2_conn_early_data_rejected`. All connection states altered +during early data transmission are undone. The library does not +retransmit early data to server as 1RTT data. If an application +wishes to resend data, it has to reopen streams and writes data again. +See `ngtcp2_conn_early_data_rejected`. + +Stream data ownership +-------------------------------- + +Stream data passed to :type:`ngtcp2_conn` must be held by application +until :member:`ngtcp2_callbacks.acked_stream_data_offset` callbacks is +invoked, telling that the those data are acknowledged by the remote +endpoint and no longer used by the library. + +Timers +------ + +The library does not ask an operating system for any timestamp. +Instead, an application has to supply timestamp to the library. The +type of timestamp in ngtcp2 library is :type:`ngtcp2_tstamp` which is +nanosecond resolution. The library only cares the difference of +timestamp, so it does not have to be a system clock. A monotonic +clock should work better. It should be same clock passed to +:member:`ngtcp2_settings.initial_ts`. The duration in ngtcp2 library +is :type:`ngtcp2_duration` which is also nanosecond resolution. + +`ngtcp2_conn_get_expiry()` tells an application when timer fires. +When it fires, call `ngtcp2_conn_handle_expiry()`. If it returns +:macro:`NGTCP2_ERR_IDLE_CLOSE`, it means that an idle timer has fired +for this particular connection. In this case, drop the connection +without calling `ngtcp2_conn_write_connection_close()`. Otherwise, +call `ngtcp2_conn_writev_stream()`. After calling +`ngtcp2_conn_handle_expiry()` and `ngtcp2_conn_writev_stream()`, new +expiry is set. The application should call `ngtcp2_conn_get_expiry()` +to get a new deadline. + +Please note that :type:`ngtcp2_tstamp` of value ``UINT64_MAX`` is +treated as an invalid timestamp. Do not pass ``UINT64_MAX`` to any +ngtcp2 functions which take :type:`ngtcp2_tstamp` unless it is +explicitly allowed. + +Connection migration +-------------------- + +In QUIC, client application can migrate to a new local address. +`ngtcp2_conn_initiate_immediate_migration()` migrates to a new local +address without checking reachability. On the other hand, +`ngtcp2_conn_initiate_migration()` migrates to a new local address +after a new path is validated (thus reachability is established). + +Closing connection abruptly +--------------------------- + +In order to close QUIC connection abruptly, call +`ngtcp2_conn_write_connection_close()` and get a terminal packet. +After the call, the connection enters the closing state. + +The closing and draining state +------------------------------ + +After the successful call of `ngtcp2_conn_write_connection_close()`, +the connection enters the closing state. When +`ngtcp2_conn_read_pkt()` returns :macro:`NGTCP2_ERR_DRAINING`, the +connection has entered the draining state. In these states, +`ngtcp2_conn_writev_stream()` and `ngtcp2_conn_read_pkt()` return an +error (either :macro:`NGTCP2_ERR_CLOSING` or +:macro:`NGTCP2_ERR_DRAINING` depending on the state). +`ngtcp2_conn_write_connection_close()` returns 0 in these states. If +an application needs to send a packet containing CONNECTION_CLOSE +frame in the closing state, resend the packet produced by the first +call of `ngtcp2_conn_write_connection_close()`. Therefore, after a +connection has entered one of these states, the application can +discard :type:`ngtcp2_conn` object. The closing and draining state +should persist at least 3 times the current PTO. + +Error handling in general +------------------------- + +In general, when error is returned from the ngtcp2 library function, +call `ngtcp2_conn_write_connection_close()` to get terminal packet. +If the successful call of the function creates non-empty packet, the +QUIC connection enters the closing state. + +If :macro:`NGTCP2_ERR_DROP_CONN` is returned from +`ngtcp2_conn_read_pkt`, a connection should be dropped without calling +`ngtcp2_conn_write_connection_close()`. Similarly, if +:macro:`NGTCP2_ERR_IDLE_CLOSE` is returned from +`ngtcp2_conn_handle_expiry`, a connection should be dropped without +calling `ngtcp2_conn_write_connection_close()`. If +:macro:`NGTCP2_ERR_DRAINING` is returned from `ngtcp2_conn_read_pkt`, +a connection has entered the draining state, and no further packet +transmission is allowed. + +The following error codes must be considered as transitional, and +application should keep connection alive: + +* :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED` +* :macro:`NGTCP2_ERR_STREAM_SHUT_WR` +* :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` +* :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` + +Version negotiation +------------------- + +Version negotiation is configured with the following +:type:`ngtcp2_settings` fields: + +* :member:`ngtcp2_settings.preferred_versions` and + :member:`ngtcp2_settings.preferred_versionslen` +* :member:`ngtcp2_settings.other_versions` and + :member:`ngtcp2_settings.other_versionslen` +* :member:`ngtcp2_settings.original_version` + +*client_chosen_version* passed to `ngtcp2_conn_client_new` also +influence the version negotiation process. + +By default, client sends *client_chosen_version* passed to +`ngtcp2_conn_client_new` in other_versions field of +version_information QUIC transport parameter. That means there is no +chance for server to select the other compatible version. Meanwhile, +ngtcp2 supports QUIC v2 draft version +(:macro:`NGTCP2_PROTO_VER_V2_DRAFT`). Including both +:macro:`NGTCP2_PROTO_VER_V1` and :macro:`NGTCP2_PROTO_VER_V2_DRAFT` in +:member:`ngtcp2_settings.other_versions` field allows server to choose +:macro:`NGTCP2_PROTO_VER_V2_DRAFT` which is compatible to +:macro:`NGTCP2_PROTO_VER_V1`. + +By default, server sends :macro:`NGTCP2_PROTO_VER_V1` in +other_versions field of version_information QUIC transport parameter. +Because there is no particular preferred versions specified, server +will accept any supported version. In order to set the version +preference, specify :member:`ngtcp2_settings.preferred_versions` +field. If it is specified, server sends them in other_versions field +of version_information QUIC transport parameter unless +:member:`ngtcp2_settings.other_versionslen` is not zero. Specifying +:member:`ngtcp2_settings.other_versions` overrides the above mentioned +default behavior. Even if there is no overlap between +:member:`ngtcp2_settings.preferred_versions` and other_versions field +plus *client_chosen_version* from client, as long as +*client_chosen_version* is supported by server, server accepts +*client_chosen_version*. + +If client receives Version Negotiation packet from server, +`ngtcp2_conn_read_pkt` returns +:macro:`NGTCP2_ERR_RECV_VERSION_NEGOTIATION`. +:member:`ngtcp2_callbacks.recv_version_negotiation` is also invoked if +set. It will provide the versions contained in the packet. Client +then either gives up the connection attempt, or selects the version +from Version Negotiation packet, and starts new connection attempt +with that version. In the latter case, the initial version that used +in the first connection attempt must be set to +:member:`ngtcp2_settings.original_version`. The client version +preference that is used when selecting a version from Version +Negotiation packet must be set to +:member:`ngtcp2_settings.preferred_versions`. +:member:`ngtcp2_settings.other_versions` must include the selected +version. The selected version becomes *client_chosen_version* in the +second connection attempt, and must be passed to +`ngtcp2_conn_client_new`. + +Server never know whether client reacted upon Version Negotiation +packet or not, and there is no particular setup for server to make +this incompatible version negotiation work. + +Thread safety +------------- + +ngtcp2 library is thread-safe as long as a single :type:`ngtcp2_conn` +object is accessed by a single thread at a time. For multi-threaded +applications, it is recommended to create :type:`ngtcp2_conn` objects +per thread to avoid locks. diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..d6141d9 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,39 @@ +FROM debian:11 as build + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + git g++ clang-11 make binutils autoconf automake autotools-dev libtool \ + pkg-config libev-dev libjemalloc-dev \ + ca-certificates mime-support && \ + git clone --depth 1 -b OpenSSL_1_1_1s+quic https://github.com/quictls/openssl && \ + cd openssl && ./config --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ + git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ + cd nghttp3 && autoreconf -i && \ + ./configure --enable-lib-only CC=clang-11 CXX=clang++-11 && \ + make -j$(nproc) && make install-strip && cd .. && rm -rf nghttp3 && \ + git clone --depth 1 https://github.com/ngtcp2/ngtcp2 && \ + cd ngtcp2 && autoreconf -i && \ + ./configure \ + CC=clang-11 \ + CXX=clang++-11 \ + LIBTOOL_LDFLAGS="-static-libtool-libs" \ + OPENSSL_LIBS="-l:libssl.a -l:libcrypto.a -ldl -pthread" \ + LIBEV_LIBS="-l:libev.a" \ + JEMALLOC_LIBS="-l:libjemalloc.a -lm" && \ + make -j$(nproc) && \ + strip examples/client examples/server && \ + cp examples/client examples/server /usr/local/bin && \ + cd .. && rm -rf ngtcp2 && \ + apt-get -y purge \ + git g++ clang-11 make binutils autoconf automake autotools-dev libtool \ + pkg-config libev-dev libjemalloc-dev \ + ca-certificates && \ + apt-get -y autoremove --purge && \ + rm -rf /var/log/* + +FROM gcr.io/distroless/cc-debian11:latest-amd64 + +COPY --from=build /usr/local/bin/client /usr/local/bin/server /usr/local/bin/ +COPY --from=build /etc/mime.types /etc/ + +CMD ["/usr/local/bin/client"] diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..b2e3553 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,15 @@ +client +server +examplestest +h09client +h09server +gtlsclient +gtlsserver +bsslclient +bsslserver +ptlsclient +ptlsserver +simpleclient +wsslclient +wsslserver +gtlssimpleclient diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..09701b8 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,359 @@ +# ngtcp2 + +# Copyright (c) 2017 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if(LIBEV_FOUND AND HAVE_OPENSSL AND LIBNGHTTP3_FOUND) + set(client_SOURCES + client.cc + client_base.cc + debug.cc + util.cc + shared.cc + tls_client_context_openssl.cc + tls_client_session_openssl.cc + tls_session_base_openssl.cc + util_openssl.cc + ) + + set(server_SOURCES + server.cc + server_base.cc + debug.cc + util.cc + http.cc + shared.cc + tls_server_context_openssl.cc + tls_server_session_openssl.cc + tls_session_base_openssl.cc + util_openssl.cc + ) + + set(ossl_INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/lib/includes + ${CMAKE_BINARY_DIR}/lib/includes + ${CMAKE_SOURCE_DIR}/third-party + ${CMAKE_SOURCE_DIR}/crypto/includes + + ${JEMALLOC_INCLUDE_DIRS} + ${OPENSSL_INCLUDE_DIRS} + ${LIBEV_INCLUDE_DIRS} + ${LIBNGHTTP3_INCLUDE_DIRS} + ) + + set(ossl_LIBS + ngtcp2_crypto_openssl + ngtcp2 + ${JEMALLOC_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${LIBEV_LIBRARIES} + ${LIBNGHTTP3_LIBRARIES} + ) + + add_executable(client ${client_SOURCES} $<TARGET_OBJECTS:http-parser>) + add_executable(server ${server_SOURCES} $<TARGET_OBJECTS:http-parser>) + set_target_properties(client PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_OPENSSL -DWITH_EXAMPLE_OPENSSL" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + set_target_properties(server PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_OPENSSL -DWITH_EXAMPLE_OPENSSL" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + target_include_directories(client PUBLIC ${ossl_INCLUDE_DIRS}) + target_include_directories(server PUBLIC ${ossl_INCLUDE_DIRS}) + target_link_libraries(client ${ossl_LIBS}) + target_link_libraries(server ${ossl_LIBS}) + + # TODO prevent client and example servers from being installed? +endif() + +if(LIBEV_FOUND AND HAVE_GNUTLS AND LIBNGHTTP3_FOUND) + set(gtlsclient_SOURCES + client.cc + client_base.cc + debug.cc + util.cc + shared.cc + tls_client_context_gnutls.cc + tls_client_session_gnutls.cc + tls_session_base_gnutls.cc + util_gnutls.cc + ) + + set(gtlsserver_SOURCES + server.cc + server_base.cc + debug.cc + util.cc + http.cc + shared.cc + tls_server_context_gnutls.cc + tls_server_session_gnutls.cc + tls_session_base_gnutls.cc + util_gnutls.cc + ) + + set(gtls_INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/lib/includes + ${CMAKE_BINARY_DIR}/lib/includes + ${CMAKE_SOURCE_DIR}/third-party + ${CMAKE_SOURCE_DIR}/crypto/includes + + ${JEMALLOC_INCLUDE_DIRS} + ${GNUTLS_INCLUDE_DIRS} + ${LIBEV_INCLUDE_DIRS} + ${LIBNGHTTP3_INCLUDE_DIRS} + ) + + set(gtls_LIBS + ngtcp2_crypto_gnutls + ngtcp2 + ${JEMALLOC_LIBRARIES} + ${GNUTLS_LIBRARIES} + ${LIBEV_LIBRARIES} + ${LIBNGHTTP3_LIBRARIES} + ) + + add_executable(gtlsclient ${gtlsclient_SOURCES} $<TARGET_OBJECTS:http-parser>) + add_executable(gtlsserver ${gtlsserver_SOURCES} $<TARGET_OBJECTS:http-parser>) + set_target_properties(gtlsclient PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_GNUTLS -DWITH_EXAMPLE_GNUTLS" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + set_target_properties(gtlsserver PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_GNUTLS -DWITH_EXAMPLE_GNUTLS" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + target_include_directories(gtlsclient PUBLIC ${gtls_INCLUDE_DIRS}) + target_include_directories(gtlsserver PUBLIC ${gtls_INCLUDE_DIRS}) + target_link_libraries(gtlsclient ${gtls_LIBS}) + target_link_libraries(gtlsserver ${gtls_LIBS}) + + # TODO prevent gtlsclient and example gtlsservers from being installed? +endif() + +if(LIBEV_FOUND AND HAVE_BORINGSSL AND LIBNGHTTP3_FOUND) + set(bsslclient_SOURCES + client.cc + client_base.cc + debug.cc + util.cc + shared.cc + tls_client_context_boringssl.cc + tls_client_session_boringssl.cc + tls_session_base_openssl.cc + util_openssl.cc + ) + + set(bsslserver_SOURCES + server.cc + server_base.cc + debug.cc + util.cc + http.cc + shared.cc + tls_server_context_boringssl.cc + tls_server_session_boringssl.cc + tls_session_base_openssl.cc + util_openssl.cc + ) + + set(bssl_INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/lib/includes + ${CMAKE_BINARY_DIR}/lib/includes + ${CMAKE_SOURCE_DIR}/third-party + ${CMAKE_SOURCE_DIR}/crypto/includes + + ${JEMALLOC_INCLUDE_DIRS} + ${BORINGSSL_INCLUDE_DIRS} + ${LIBEV_INCLUDE_DIRS} + ${LIBNGHTTP3_INCLUDE_DIRS} + ) + + set(bssl_LIBS + ngtcp2_crypto_boringssl_static + ngtcp2 + ${JEMALLOC_LIBRARIES} + ${BORINGSSL_LIBRARIES} + ${LIBEV_LIBRARIES} + ${LIBNGHTTP3_LIBRARIES} + ) + + add_executable(bsslclient ${bsslclient_SOURCES} $<TARGET_OBJECTS:http-parser>) + add_executable(bsslserver ${bsslserver_SOURCES} $<TARGET_OBJECTS:http-parser>) + set_target_properties(bsslclient PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_BORINGSSL -DWITH_EXAMPLE_BORINGSSL" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + set_target_properties(bsslserver PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_BORINGSSL -DWITH_EXAMPLE_BORINGSSL" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + target_include_directories(bsslclient PUBLIC ${bssl_INCLUDE_DIRS}) + target_include_directories(bsslserver PUBLIC ${bssl_INCLUDE_DIRS}) + target_link_libraries(bsslclient ${bssl_LIBS}) + target_link_libraries(bsslserver ${bssl_LIBS}) + + # TODO prevent bsslclient and example bsslservers from being installed? +endif() + +if(LIBEV_FOUND AND HAVE_PICOTLS AND LIBNGHTTP3_FOUND) + set(ptlsclient_SOURCES + client.cc + client_base.cc + debug.cc + util.cc + shared.cc + tls_client_context_picotls.cc + tls_client_session_picotls.cc + tls_session_base_picotls.cc + util_openssl.cc + ) + + set(ptlsserver_SOURCES + server.cc + server_base.cc + debug.cc + util.cc + http.cc + shared.cc + tls_server_context_picotls.cc + tls_server_session_picotls.cc + tls_session_base_picotls.cc + util_openssl.cc + ) + + set(ptls_INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/lib/includes + ${CMAKE_BINARY_DIR}/lib/includes + ${CMAKE_SOURCE_DIR}/third-party + ${CMAKE_SOURCE_DIR}/crypto/includes + + ${JEMALLOC_INCLUDE_DIRS} + ${PICOTLS_INCLUDE_DIRS} + ${VANILLA_OPENSSL_INCLUDE_DIRS} + ${LIBEV_INCLUDE_DIRS} + ${LIBNGHTTP3_INCLUDE_DIRS} + ) + + set(ptls_LIBS + ngtcp2_crypto_picotls_static + ngtcp2 + ${JEMALLOC_LIBRARIES} + ${PICOTLS_LIBRARIES} + ${VANILLA_OPENSSL_LIBRARIES} + ${LIBEV_LIBRARIES} + ${LIBNGHTTP3_LIBRARIES} + ) + + add_executable(ptlsclient ${ptlsclient_SOURCES} $<TARGET_OBJECTS:http-parser>) + add_executable(ptlsserver ${ptlsserver_SOURCES} $<TARGET_OBJECTS:http-parser>) + set_target_properties(ptlsclient PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_PICOTLS -DWITH_EXAMPLE_PICOTLS" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + set_target_properties(ptlsserver PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_PICOTLS -DWITH_EXAMPLE_PICOTLS" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + target_include_directories(ptlsclient PUBLIC ${ptls_INCLUDE_DIRS}) + target_include_directories(ptlsserver PUBLIC ${ptls_INCLUDE_DIRS}) + target_link_libraries(ptlsclient ${ptls_LIBS}) + target_link_libraries(ptlsserver ${ptls_LIBS}) + + # TODO prevent ptlsclient and example ptlsservers from being installed? +endif() + +if(LIBEV_FOUND AND HAVE_WOLFSSL AND LIBNGHTTP3_FOUND) + set(wsslclient_SOURCES + client.cc + client_base.cc + debug.cc + util.cc + shared.cc + tls_client_context_wolfssl.cc + tls_client_session_wolfssl.cc + tls_session_base_wolfssl.cc + util_wolfssl.cc + ) + + set(wsslserver_SOURCES + server.cc + server_base.cc + debug.cc + util.cc + http.cc + shared.cc + tls_server_context_wolfssl.cc + tls_server_session_wolfssl.cc + tls_session_base_wolfssl.cc + util_wolfssl.cc + ) + + set(wolfssl_INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/lib/includes + ${CMAKE_BINARY_DIR}/lib/includes + ${CMAKE_SOURCE_DIR}/third-party + ${CMAKE_SOURCE_DIR}/crypto/includes + + ${JEMALLOC_INCLUDE_DIRS} + ${WOLFSSL_INCLUDE_DIRS} + ${LIBEV_INCLUDE_DIRS} + ${LIBNGHTTP3_INCLUDE_DIRS} + ) + + set(wolfssl_LIBS + ngtcp2_crypto_wolfssl_static + ngtcp2 + ${JEMALLOC_LIBRARIES} + ${WOLFSSL_LIBRARIES} + ${LIBEV_LIBRARIES} + ${LIBNGHTTP3_LIBRARIES} + ) + + add_executable(wsslclient ${wsslclient_SOURCES} $<TARGET_OBJECTS:http-parser>) + add_executable(wsslserver ${wsslserver_SOURCES} $<TARGET_OBJECTS:http-parser>) + set_target_properties(wsslclient PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_WOLFSSL -DWITH_EXAMPLE_WOLFSSL" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + set_target_properties(wsslserver PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS} -DENABLE_EXAMPLE_WOLFSSL -DWITH_EXAMPLE_WOLFSSL" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + ) + target_include_directories(wsslclient PUBLIC ${wolfssl_INCLUDE_DIRS}) + target_include_directories(wsslserver PUBLIC ${wolfssl_INCLUDE_DIRS}) + target_link_libraries(wsslclient ${wolfssl_LIBS}) + target_link_libraries(wsslserver ${wolfssl_LIBS}) + + # TODO prevent wsslclient and example wsslserver from being installed? +endif() diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..66bfbe5 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,226 @@ +# ngtcp2 + +# Copyright (c) 2017 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) +AM_CXXFLAGS = $(WARNCXXFLAGS) $(DEBUGCFLAGS) +AM_CPPFLAGS = \ + -I$(top_srcdir)/lib/includes \ + -I$(top_builddir)/lib/includes \ + -I$(top_srcdir)/crypto/includes \ + -I$(top_srcdir)/third-party \ + @LIBEV_CFLAGS@ \ + @LIBNGHTTP3_CFLAGS@ \ + @DEFS@ \ + @EXTRA_DEFS@ +AM_LDFLAGS = -no-install \ + @LIBTOOL_LDFLAGS@ +LDADD = $(top_builddir)/lib/libngtcp2.la \ + $(top_builddir)/third-party/libhttp-parser.la \ + @LIBEV_LIBS@ \ + @LIBNGHTTP3_LIBS@ + +SERVER_SRCS = \ + server_base.cc server_base.h \ + tls_server_context.h \ + tls_server_session.h \ + template.h \ + debug.cc debug.h \ + util.cc util.h \ + shared.cc shared.h \ + http.cc http.h \ + network.h + +CLIENT_SRCS = \ + client_base.cc client_base.h \ + tls_client_context.h \ + tls_client_session.h \ + template.h \ + debug.cc debug.h \ + util.cc util.h \ + shared.cc shared.h \ + network.h + +noinst_PROGRAMS = + +if ENABLE_EXAMPLE_OPENSSL +noinst_PROGRAMS += client server h09client h09server simpleclient + +simpleclient_CPPFLAGS = ${AM_CPPFLAGS} \ + @JEMALLOC_CFLAGS@ @OPENSSL_CFLAGS@ -DWITH_EXAMPLE_OPENSSL +simpleclient_LDADD = ${LDADD} \ + $(top_builddir)/crypto/openssl/libngtcp2_crypto_openssl.la \ + @OPENSSL_LIBS@ \ + @JEMALLOC_LIBS@ +simpleclient_SOURCES = simpleclient.c + +client_CPPFLAGS = ${AM_CPPFLAGS} \ + @JEMALLOC_CFLAGS@ @OPENSSL_CFLAGS@ -DWITH_EXAMPLE_OPENSSL +client_LDADD = ${LDADD} \ + $(top_builddir)/crypto/openssl/libngtcp2_crypto_openssl.la \ + @OPENSSL_LIBS@ \ + @JEMALLOC_LIBS@ +client_SOURCES = client.cc client.h ${CLIENT_SRCS} \ + tls_client_context_openssl.cc tls_client_context_openssl.h \ + tls_client_session_openssl.cc tls_client_session_openssl.h \ + tls_session_base_openssl.cc tls_session_base_openssl.h \ + util_openssl.cc + +server_CPPFLAGS = ${client_CPPFLAGS} +server_LDADD = ${client_LDADD} +server_SOURCES = server.cc server.h ${SERVER_SRCS} \ + tls_server_context_openssl.cc tls_server_context_openssl.h \ + tls_server_session_openssl.cc tls_server_session_openssl.h \ + tls_session_base_openssl.cc tls_session_base_openssl.h \ + util_openssl.cc + +h09client_CPPFLAGS = ${client_CPPFLAGS} +h09client_LDADD = ${client_LDADD} +h09client_SOURCES = h09client.cc h09client.h ${CLIENT_SRCS} \ + tls_client_context_openssl.cc tls_client_context_openssl.h \ + tls_client_session_openssl.cc tls_client_session_openssl.h \ + tls_session_base_openssl.cc tls_session_base_openssl.h \ + util_openssl.cc + +h09server_CPPFLAGS = ${client_CPPFLAGS} +h09server_LDADD = ${client_LDADD} +h09server_SOURCES = h09server.cc h09server.h ${SERVER_SRCS} \ + tls_server_context_openssl.cc tls_server_context_openssl.h \ + tls_server_session_openssl.cc tls_server_session_openssl.h \ + tls_session_base_openssl.cc tls_session_base_openssl.h \ + util_openssl.cc +endif # ENABLE_EXAMPLE_OPENSSL + +if ENABLE_EXAMPLE_GNUTLS +noinst_PROGRAMS += gtlsclient gtlsserver gtlssimpleclient + +gtlssimpleclient_CPPFLAGS = ${AM_CPPFLAGS} \ + @JEMALLOC_CFLAGS@ @GNUTLS_CFLAGS@ -DWITH_EXAMPLE_OPENSSL +gtlssimpleclient_LDADD = ${LDADD} \ + $(top_builddir)/crypto/gnutls/libngtcp2_crypto_gnutls.la \ + @GNUTLS_LIBS@ \ + @JEMALLOC_LIBS@ +gtlssimpleclient_SOURCES = gtlssimpleclient.c + +gtlsclient_CPPFLAGS = ${AM_CPPFLAGS} \ + @JEMALLOC_CFLAGS@ @GNUTLS_CFLAGS@ -DWITH_EXAMPLE_GNUTLS +gtlsclient_LDADD = ${LDADD} \ + $(top_builddir)/crypto/gnutls/libngtcp2_crypto_gnutls.la \ + @GNUTLS_LIBS@ \ + @JEMALLOC_LIBS@ +gtlsclient_SOURCES = client.cc client.h ${CLIENT_SRCS} \ + tls_client_context_gnutls.cc tls_client_context_gnutls.h \ + tls_client_session_gnutls.cc tls_client_session_gnutls.h \ + tls_session_base_gnutls.cc tls_session_base_gnutls.h \ + util_gnutls.cc + +gtlsserver_CPPFLAGS = ${gtlsclient_CPPFLAGS} +gtlsserver_LDADD = ${gtlsclient_LDADD} \ + $(top_builddir)/crypto/gnutls/libngtcp2_crypto_gnutls.la \ + @GNUTLS_LIBS@ +gtlsserver_SOURCES = server.cc server.h ${SERVER_SRCS} \ + tls_server_context_gnutls.cc tls_server_context_gnutls.h \ + tls_server_session_gnutls.cc tls_server_session_gnutls.h \ + tls_session_base_gnutls.cc tls_session_base_gnutls.h \ + util_gnutls.cc +endif # ENABLE_EXAMPLE_GNUTLS + +if ENABLE_EXAMPLE_BORINGSSL +noinst_PROGRAMS += bsslclient bsslserver + +bsslclient_CPPFLAGS = ${AM_CPPFLAGS} @BORINGSSL_CFLAGS@ -DWITH_EXAMPLE_BORINGSSL +bsslclient_LDADD = ${LDADD} \ + $(top_builddir)/crypto/boringssl/libngtcp2_crypto_boringssl.a \ + @BORINGSSL_LIBS@ \ + @JEMALLOC_LIBS@ +bsslclient_SOURCES = client.cc client.h ${CLIENT_SRCS} \ + tls_client_context_boringssl.cc tls_client_context_boringssl.h \ + tls_client_session_boringssl.cc tls_client_session_boringssl.h \ + tls_session_base_openssl.cc tls_session_base_openssl.h \ + util_openssl.cc + +bsslserver_CPPFLAGS = ${bsslclient_CPPFLAGS} +bsslserver_LDADD = ${bsslclient_LDADD} +bsslserver_SOURCES = server.cc server.h ${SERVER_SRCS} \ + tls_server_context_boringssl.cc tls_server_context_boringssl.h \ + tls_server_session_boringssl.cc tls_server_session_boringssl.h \ + tls_session_base_openssl.cc tls_session_base_openssl.h \ + util_openssl.cc +endif # ENABLE_EXAMPLE_BORINGSSL + +if ENABLE_EXAMPLE_PICOTLS +noinst_PROGRAMS += ptlsclient ptlsserver + +ptlsclient_CPPFLAGS = ${AM_CPPFLAGS} @PICOTLS_CFLAGS@ @VANILLA_OPENSSL_CFLAGS@ \ + -DWITH_EXAMPLE_PICOTLS +ptlsclient_LDADD = ${LDADD} \ + $(top_builddir)/crypto/picotls/libngtcp2_crypto_picotls.a \ + @PICOTLS_LIBS@ @VANILLA_OPENSSL_LIBS@ \ + @JEMALLOC_LIBS@ +ptlsclient_SOURCES = client.cc client.h ${CLIENT_SRCS} \ + tls_client_context_picotls.cc tls_client_context_picotls.h \ + tls_client_session_picotls.cc tls_client_session_picotls.h \ + tls_session_base_picotls.cc tls_session_base_picotls.h \ + util_openssl.cc + +ptlsserver_CPPFLAGS = ${ptlsclient_CPPFLAGS} +ptlsserver_LDADD = ${ptlsclient_LDADD} +ptlsserver_SOURCES = server.cc server.h ${SERVER_SRCS} \ + tls_server_context_picotls.cc tls_server_context_picotls.h \ + tls_server_session_picotls.cc tls_server_session_picotls.h \ + tls_session_base_picotls.cc tls_session_base_picotls.h \ + util_openssl.cc +endif # ENABLE_EXAMPLE_PICOTLS + +if ENABLE_EXAMPLE_WOLFSSL +noinst_PROGRAMS += wsslclient wsslserver + +wsslclient_CPPFLAGS = ${AM_CPPFLAGS} @WOLFSSL_CFLAGS@ -DWITH_EXAMPLE_WOLFSSL +wsslclient_LDADD = ${LDADD} \ + $(top_builddir)/crypto/wolfssl/libngtcp2_crypto_wolfssl.la \ + @WOLFSSL_LIBS@ \ + @JEMALLOC_LIBS@ +wsslclient_SOURCES = client.cc client.h ${CLIENT_SRCS} \ + tls_client_context_wolfssl.cc tls_client_context_wolfssl.h \ + tls_client_session_wolfssl.cc tls_client_session_wolfssl.h \ + tls_session_base_wolfssl.cc tls_session_base_wolfssl.h \ + util_wolfssl.cc + +wsslserver_CPPFLAGS = ${wsslclient_CPPFLAGS} +wsslserver_LDADD = ${wsslclient_LDADD} +wsslserver_SOURCES = server.cc server.h ${SERVER_SRCS} \ + tls_server_context_wolfssl.cc tls_server_context_wolfssl.h \ + tls_server_session_wolfssl.cc tls_server_session_wolfssl.h \ + tls_session_base_wolfssl.cc tls_session_base_wolfssl.h \ + util_wolfssl.cc +endif # ENABLE_EXAMPLE_WOLFSSL + +if HAVE_CUNIT +check_PROGRAMS = examplestest +examplestest_SOURCES = examplestest.cc \ + util_test.cc util_test.h util.cc util.h +examplestest_CPPFLAGS = ${AM_CPPFLAGS} @JEMALLOC_CFLAGS@ +examplestest_LDADD = ${LDADD} @CUNIT_LIBS@ @JEMALLOC_LIBS@ + +TESTS = examplestest +endif # HAVE_CUNIT diff --git a/examples/client.cc b/examples/client.cc new file mode 100644 index 0000000..2a2e2b3 --- /dev/null +++ b/examples/client.cc @@ -0,0 +1,3052 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include <cstdlib> +#include <cassert> +#include <cerrno> +#include <iostream> +#include <algorithm> +#include <memory> +#include <fstream> +#include <iomanip> + +#include <unistd.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <netdb.h> +#include <sys/mman.h> + +#include <http-parser/http_parser.h> + +#include "client.h" +#include "network.h" +#include "debug.h" +#include "util.h" +#include "shared.h" + +using namespace ngtcp2; +using namespace std::literals; + +namespace { +auto randgen = util::make_mt19937(); +} // namespace + +namespace { +constexpr size_t max_preferred_versionslen = 4; +} // namespace + +Config config{}; + +Stream::Stream(const Request &req, int64_t stream_id) + : req(req), stream_id(stream_id), fd(-1) {} + +Stream::~Stream() { + if (fd != -1) { + close(fd); + } +} + +int Stream::open_file(const std::string_view &path) { + assert(fd == -1); + + std::string_view filename; + + auto it = std::find(std::rbegin(path), std::rend(path), '/').base(); + if (it == std::end(path)) { + filename = "index.html"sv; + } else { + filename = std::string_view{it, static_cast<size_t>(std::end(path) - it)}; + if (filename == ".."sv || filename == "."sv) { + std::cerr << "Invalid file name: " << filename << std::endl; + return -1; + } + } + + auto fname = std::string{config.download}; + fname += '/'; + fname += filename; + + fd = open(fname.c_str(), O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd == -1) { + std::cerr << "open: Could not open file " << fname << ": " + << strerror(errno) << std::endl; + return -1; + } + + return 0; +} + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto c = static_cast<Client *>(w->data); + + c->on_write(); +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto ep = static_cast<Endpoint *>(w->data); + auto c = ep->client; + + if (c->on_read(*ep) != 0) { + return; + } + + c->on_write(); +} +} // namespace + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + int rv; + auto c = static_cast<Client *>(w->data); + + rv = c->handle_expiry(); + if (rv != 0) { + return; + } + + c->on_write(); +} +} // namespace + +namespace { +void change_local_addrcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto c = static_cast<Client *>(w->data); + + c->change_local_addr(); +} +} // namespace + +namespace { +void key_updatecb(struct ev_loop *loop, ev_timer *w, int revents) { + auto c = static_cast<Client *>(w->data); + + if (c->initiate_key_update() != 0) { + c->disconnect(); + } +} +} // namespace + +namespace { +void delay_streamcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto c = static_cast<Client *>(w->data); + + ev_timer_stop(loop, w); + c->on_extend_max_streams(); + c->on_write(); +} +} // namespace + +namespace { +void siginthandler(struct ev_loop *loop, ev_signal *w, int revents) { + ev_break(loop, EVBREAK_ALL); +} +} // namespace + +Client::Client(struct ev_loop *loop, uint32_t client_chosen_version, + uint32_t original_version) + : remote_addr_{}, + loop_(loop), + httpconn_(nullptr), + addr_(nullptr), + port_(nullptr), + nstreams_done_(0), + nstreams_closed_(0), + nkey_update_(0), + client_chosen_version_(client_chosen_version), + original_version_(original_version), + early_data_(false), + should_exit_(false), + should_exit_on_handshake_confirmed_(false), + handshake_confirmed_(false), + tx_{} { + ev_io_init(&wev_, writecb, 0, EV_WRITE); + wev_.data = this; + ev_timer_init(&timer_, timeoutcb, 0., 0.); + timer_.data = this; + ev_timer_init(&change_local_addr_timer_, change_local_addrcb, + static_cast<double>(config.change_local_addr) / NGTCP2_SECONDS, + 0.); + change_local_addr_timer_.data = this; + ev_timer_init(&key_update_timer_, key_updatecb, + static_cast<double>(config.key_update) / NGTCP2_SECONDS, 0.); + key_update_timer_.data = this; + ev_timer_init(&delay_stream_timer_, delay_streamcb, + static_cast<double>(config.delay_stream) / NGTCP2_SECONDS, 0.); + delay_stream_timer_.data = this; + ev_signal_init(&sigintev_, siginthandler, SIGINT); +} + +Client::~Client() { + disconnect(); + + if (httpconn_) { + nghttp3_conn_del(httpconn_); + httpconn_ = nullptr; + } +} + +void Client::disconnect() { + tx_.send_blocked = false; + + handle_error(); + + config.tx_loss_prob = 0; + + ev_timer_stop(loop_, &delay_stream_timer_); + ev_timer_stop(loop_, &key_update_timer_); + ev_timer_stop(loop_, &change_local_addr_timer_); + ev_timer_stop(loop_, &timer_); + + ev_io_stop(loop_, &wev_); + + for (auto &ep : endpoints_) { + ev_io_stop(loop_, &ep.rev); + close(ep.fd); + } + + endpoints_.clear(); + + ev_signal_stop(loop_, &sigintev_); +} + +namespace { +int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data) { + if (!config.quiet && !config.no_quic_dump) { + debug::print_crypto_data(crypto_level, data, datalen); + } + + return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data, + datalen, user_data); +} +} // namespace + +namespace { +int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) { + if (!config.quiet && !config.no_quic_dump) { + debug::print_stream_data(stream_id, data, datalen); + } + + auto c = static_cast<Client *>(user_data); + + if (c->recv_stream_data(flags, stream_id, data, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t offset, uint64_t datalen, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + if (c->acked_stream_data_offset(stream_id, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +namespace { +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (!config.quiet) { + debug::handshake_completed(conn, user_data); + } + + if (c->handshake_completed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::handshake_completed() { + if (early_data_ && !tls_session_.get_early_data_accepted()) { + if (!config.quiet) { + std::cerr << "Early data was rejected by server" << std::endl; + } + + // Some TLS backends only report early data rejection after + // handshake completion (e.g., OpenSSL). For TLS backends which + // report it early (e.g., BoringSSL and PicoTLS), the following + // functions are noop. + if (auto rv = ngtcp2_conn_early_data_rejected(conn_); rv != 0) { + std::cerr << "ngtcp2_conn_early_data_rejected: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + if (setup_httpconn() != 0) { + return -1; + } + } + + if (!config.quiet) { + std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name() + << std::endl; + std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn() + << std::endl; + } + + if (config.tp_file) { + auto params = ngtcp2_conn_get_remote_transport_params(conn_); + + if (write_transport_params(config.tp_file, params) != 0) { + std::cerr << "Could not write transport parameters in " << config.tp_file + << std::endl; + } + } + + return 0; +} + +namespace { +int handshake_confirmed(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (!config.quiet) { + debug::handshake_confirmed(conn, user_data); + } + + if (c->handshake_confirmed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::handshake_confirmed() { + handshake_confirmed_ = true; + + if (config.change_local_addr) { + start_change_local_addr_timer(); + } + if (config.key_update) { + start_key_update_timer(); + } + if (config.delay_stream) { + start_delay_stream_timer(); + } + + if (should_exit_on_handshake_confirmed_) { + should_exit_ = true; + } + + return 0; +} + +namespace { +int recv_version_negotiation(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv, void *user_data) { + auto c = static_cast<Client *>(user_data); + + c->recv_version_negotiation(sv, nsv); + + return 0; +} +} // namespace + +void Client::recv_version_negotiation(const uint32_t *sv, size_t nsv) { + offered_versions_.resize(nsv); + std::copy_n(sv, nsv, std::begin(offered_versions_)); +} + +namespace { +int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + + if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + + if (c->on_stream_close(stream_id, app_error_code) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + + if (c->on_stream_reset(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + + if (c->on_stream_stop_sending(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int extend_max_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, + void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (c->on_extend_max_streams() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { + auto dis = std::uniform_int_distribution<uint8_t>(0, 255); + std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); }); +} +} // namespace + +namespace { +int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, + size_t cidlen, void *user_data) { + if (util::generate_secure_random(cid->data, cidlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + cid->datalen = cidlen; + if (ngtcp2_crypto_generate_stateless_reset_token( + token, config.static_secret.data(), config.static_secret.size(), + cid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) { + if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (!config.quiet && config.show_secret) { + debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN); + } + + return 0; +} +} // namespace + +namespace { +int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen, + void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (c->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx, + tx_iv, current_rx_secret, current_tx_secret, + secretlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path, + ngtcp2_path_validation_result res, void *user_data) { + if (!config.quiet) { + debug::path_validation(path, res); + } + + if (flags & NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR) { + auto c = static_cast<Client *>(user_data); + + c->set_remote_addr(path->remote); + } + + return 0; +} +} // namespace + +void Client::set_remote_addr(const ngtcp2_addr &remote_addr) { + memcpy(&remote_addr_.su, remote_addr.addr, remote_addr.addrlen); + remote_addr_.len = remote_addr.addrlen; +} + +namespace { +int select_preferred_address(ngtcp2_conn *conn, ngtcp2_path *dest, + const ngtcp2_preferred_addr *paddr, + void *user_data) { + auto c = static_cast<Client *>(user_data); + Address remote_addr; + + if (config.no_preferred_addr) { + return 0; + } + + if (c->select_preferred_address(remote_addr, paddr) != 0) { + return 0; + } + + auto ep = c->endpoint_for(remote_addr); + if (!ep) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + ngtcp2_addr_copy_byte(&dest->local, &(*ep)->addr.su.sa, (*ep)->addr.len); + ngtcp2_addr_copy_byte(&dest->remote, &remote_addr.su.sa, remote_addr.len); + dest->user_data = *ep; + + return 0; +} +} // namespace + +namespace { +int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + if (c->extend_max_stream_data(stream_id, max_data) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::extend_max_stream_data(int64_t stream_id, uint64_t max_data) { + if (auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); rv != 0) { + std::cerr << "nghttp3_conn_unblock_stream: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + return 0; +} + +namespace { +int recv_new_token(ngtcp2_conn *conn, const ngtcp2_vec *token, + void *user_data) { + if (config.token_file.empty()) { + return 0; + } + + util::write_token(config.token_file, token->base, token->len); + + return 0; +} +} // namespace + +namespace { +int recv_rx_key(ngtcp2_conn *conn, ngtcp2_crypto_level level, void *user_data) { + if (level != NGTCP2_CRYPTO_LEVEL_APPLICATION) { + return 0; + } + + auto c = static_cast<Client *>(user_data); + if ((!c->get_early_data() || ngtcp2_conn_get_early_data_rejected(conn)) && + c->setup_httpconn() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int early_data_rejected(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast<Client *>(user_data); + + c->early_data_rejected(); + + return 0; +} +} // namespace + +void Client::early_data_rejected() { + nghttp3_conn_del(httpconn_); + httpconn_ = nullptr; + + nstreams_done_ = 0; + streams_.clear(); +} + +int Client::init(int fd, const Address &local_addr, const Address &remote_addr, + const char *addr, const char *port, + TLSClientContext &tls_ctx) { + endpoints_.reserve(4); + + endpoints_.emplace_back(); + auto &ep = endpoints_.back(); + ep.addr = local_addr; + ep.client = this; + ep.fd = fd; + ev_io_init(&ep.rev, readcb, fd, EV_READ); + ep.rev.data = &ep; + + remote_addr_ = remote_addr; + addr_ = addr; + port_ = port; + + auto callbacks = ngtcp2_callbacks{ + ngtcp2_crypto_client_initial_cb, + nullptr, // recv_client_initial + ::recv_crypto_data, + ::handshake_completed, + ::recv_version_negotiation, + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + do_hp_mask, + ::recv_stream_data, + ::acked_stream_data_offset, + nullptr, // stream_open + stream_close, + nullptr, // recv_stateless_reset + ngtcp2_crypto_recv_retry_cb, + extend_max_streams_bidi, + nullptr, // extend_max_streams_uni + rand, + get_new_connection_id, + nullptr, // remove_connection_id + ::update_key, + path_validation, + ::select_preferred_address, + stream_reset, + nullptr, // extend_max_remote_streams_bidi, + nullptr, // extend_max_remote_streams_uni, + ::extend_max_stream_data, + nullptr, // dcid_status + ::handshake_confirmed, + ::recv_new_token, + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + nullptr, // recv_datagram + nullptr, // ack_datagram + nullptr, // lost_datagram + ngtcp2_crypto_get_path_challenge_data_cb, + stream_stop_sending, + ngtcp2_crypto_version_negotiation_cb, + ::recv_rx_key, + nullptr, // recv_tx_key + ::early_data_rejected, + }; + + ngtcp2_cid scid, dcid; + if (config.scid_present) { + scid = config.scid; + } else { + scid.datalen = 17; + if (util::generate_secure_random(scid.data, scid.datalen) != 0) { + std::cerr << "Could not generate source connection ID" << std::endl; + return -1; + } + } + if (config.dcid.datalen == 0) { + dcid.datalen = 18; + if (util::generate_secure_random(dcid.data, dcid.datalen) != 0) { + std::cerr << "Could not generate destination connection ID" << std::endl; + return -1; + } + } else { + dcid = config.dcid; + } + + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + settings.log_printf = config.quiet ? nullptr : debug::log_printf; + if (!config.qlog_file.empty() || !config.qlog_dir.empty()) { + std::string path; + if (!config.qlog_file.empty()) { + path = config.qlog_file; + } else { + path = std::string{config.qlog_dir}; + path += '/'; + path += util::format_hex(scid.data, scid.datalen); + path += ".sqlog"; + } + qlog_ = fopen(path.c_str(), "w"); + if (qlog_ == nullptr) { + std::cerr << "Could not open qlog file " << std::quoted(path) << ": " + << strerror(errno) << std::endl; + return -1; + } + settings.qlog.write = qlog_write_cb; + } + + settings.cc_algo = config.cc_algo; + settings.initial_ts = util::timestamp(loop_); + settings.initial_rtt = config.initial_rtt; + settings.max_window = config.max_window; + settings.max_stream_window = config.max_stream_window; + if (config.max_udp_payload_size) { + settings.max_tx_udp_payload_size = config.max_udp_payload_size; + settings.no_tx_udp_payload_size_shaping = 1; + } + settings.handshake_timeout = config.handshake_timeout; + settings.no_pmtud = config.no_pmtud; + settings.ack_thresh = config.ack_thresh; + + std::string token; + + if (!config.token_file.empty()) { + std::cerr << "Reading token file " << config.token_file << std::endl; + + auto t = util::read_token(config.token_file); + if (t) { + token = std::move(*t); + settings.token.base = reinterpret_cast<uint8_t *>(token.data()); + settings.token.len = token.size(); + } + } + + if (!config.other_versions.empty()) { + settings.other_versions = config.other_versions.data(); + settings.other_versionslen = config.other_versions.size(); + } + + if (!config.preferred_versions.empty()) { + settings.preferred_versions = config.preferred_versions.data(); + settings.preferred_versionslen = config.preferred_versions.size(); + } + + settings.original_version = original_version_; + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; + params.initial_max_stream_data_bidi_remote = + config.max_stream_data_bidi_remote; + params.initial_max_stream_data_uni = config.max_stream_data_uni; + params.initial_max_data = config.max_data; + params.initial_max_streams_bidi = config.max_streams_bidi; + params.initial_max_streams_uni = config.max_streams_uni; + params.max_idle_timeout = config.timeout; + params.active_connection_id_limit = 7; + + auto path = ngtcp2_path{ + { + const_cast<sockaddr *>(&ep.addr.su.sa), + ep.addr.len, + }, + { + const_cast<sockaddr *>(&remote_addr.su.sa), + remote_addr.len, + }, + &ep, + }; + auto rv = ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path, + client_chosen_version_, &callbacks, + &settings, ¶ms, nullptr, this); + + if (rv != 0) { + std::cerr << "ngtcp2_conn_client_new: " << ngtcp2_strerror(rv) << std::endl; + return -1; + } + + if (tls_session_.init(early_data_, tls_ctx, addr_, this, + client_chosen_version_, AppProtocol::H3) != 0) { + return -1; + } + + ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle()); + + if (early_data_ && config.tp_file) { + ngtcp2_transport_params params; + if (read_transport_params(config.tp_file, ¶ms) != 0) { + std::cerr << "Could not read transport parameters from " << config.tp_file + << std::endl; + early_data_ = false; + } else { + ngtcp2_conn_set_early_remote_transport_params(conn_, ¶ms); + if (make_stream_early() != 0) { + return -1; + } + } + } + + ev_io_start(loop_, &ep.rev); + + ev_signal_start(loop_, &sigintev_); + + return 0; +} + +int Client::feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen, + const ngtcp2_pkt_info *pi, uint8_t *data, + size_t datalen) { + auto path = ngtcp2_path{ + { + const_cast<sockaddr *>(&ep.addr.su.sa), + ep.addr.len, + }, + { + const_cast<sockaddr *>(sa), + salen, + }, + const_cast<Endpoint *>(&ep), + }; + if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen, + util::timestamp(loop_)); + rv != 0) { + std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; + if (!last_error_.error_code) { + if (rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_connection_close_error_set_transport_error_tls_alert( + &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0); + } else { + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, rv, nullptr, 0); + } + } + disconnect(); + return -1; + } + return 0; +} + +int Client::on_read(const Endpoint &ep) { + std::array<uint8_t, 64_k> buf; + sockaddr_union su; + size_t pktcnt = 0; + ngtcp2_pkt_info pi; + + iovec msg_iov; + msg_iov.iov_base = buf.data(); + msg_iov.iov_len = buf.size(); + + msghdr msg{}; + msg.msg_name = &su; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint8_t))]; + msg.msg_control = msg_ctrl; + + for (;;) { + msg.msg_namelen = sizeof(su); + msg.msg_controllen = sizeof(msg_ctrl); + + auto nread = recvmsg(ep.fd, &msg, 0); + + if (nread == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + std::cerr << "recvmsg: " << strerror(errno) << std::endl; + } + break; + } + + pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family); + + if (!config.quiet) { + std::cerr << "Received packet: local=" + << util::straddr(&ep.addr.su.sa, ep.addr.len) + << " remote=" << util::straddr(&su.sa, msg.msg_namelen) + << " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread + << " bytes" << std::endl; + } + + if (debug::packet_lost(config.rx_loss_prob)) { + if (!config.quiet) { + std::cerr << "** Simulated incoming packet loss **" << std::endl; + } + break; + } + + if (feed_data(ep, &su.sa, msg.msg_namelen, &pi, buf.data(), nread) != 0) { + return -1; + } + + if (++pktcnt >= 10) { + break; + } + } + + if (should_exit_) { + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(0), nullptr, 0); + disconnect(); + return -1; + } + + update_timer(); + + return 0; +} + +int Client::handle_expiry() { + auto now = util::timestamp(loop_); + if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) { + std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_transport_error_liberr(&last_error_, rv, + nullptr, 0); + disconnect(); + return -1; + } + + return 0; +} + +int Client::on_write() { + if (tx_.send_blocked) { + if (auto rv = send_blocked_packet(); rv != 0) { + return rv; + } + + if (tx_.send_blocked) { + return 0; + } + } + + if (auto rv = write_streams(); rv != 0) { + return rv; + } + + if (should_exit_) { + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(0), nullptr, 0); + disconnect(); + return -1; + } + + update_timer(); + return 0; +} + +int Client::write_streams() { + std::array<nghttp3_vec, 16> vec; + ngtcp2_path_storage ps; + size_t pktcnt = 0; + auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_); + auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size; + auto ts = util::timestamp(loop_); + + ngtcp2_path_storage_zero(&ps); + + for (;;) { + int64_t stream_id = -1; + int fin = 0; + nghttp3_ssize sveccnt = 0; + + if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) { + sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin, + vec.data(), vec.size()); + if (sveccnt < 0) { + std::cerr << "nghttp3_conn_writev_stream: " << nghttp3_strerror(sveccnt) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(sveccnt), + nullptr, 0); + disconnect(); + return -1; + } + } + + ngtcp2_ssize ndatalen; + auto v = vec.data(); + auto vcnt = static_cast<size_t>(sveccnt); + + uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + if (fin) { + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + + ngtcp2_pkt_info pi; + + auto nwrite = ngtcp2_conn_writev_stream( + conn_, &ps.path, &pi, tx_.data.data(), max_udp_payload_size, &ndatalen, + flags, stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt, ts); + if (nwrite < 0) { + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + assert(ndatalen == -1); + nghttp3_conn_block_stream(httpconn_, stream_id); + continue; + case NGTCP2_ERR_STREAM_SHUT_WR: + assert(ndatalen == -1); + nghttp3_conn_shutdown_stream_write(httpconn_, stream_id); + continue; + case NGTCP2_ERR_WRITE_MORE: + assert(ndatalen >= 0); + if (auto rv = + nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen); + rv != 0) { + std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, + 0); + disconnect(); + return -1; + } + continue; + } + + assert(ndatalen == -1); + + std::cerr << "ngtcp2_conn_write_stream: " << ngtcp2_strerror(nwrite) + << std::endl; + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, nwrite, nullptr, 0); + disconnect(); + return -1; + } else if (ndatalen >= 0) { + if (auto rv = + nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen); + rv != 0) { + std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, + 0); + disconnect(); + return -1; + } + } + + if (nwrite == 0) { + // We are congestion limited. + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + ev_io_stop(loop_, &wev_); + return 0; + } + + auto &ep = *static_cast<Endpoint *>(ps.path.user_data); + + if (auto rv = + send_packet(ep, ps.path.remote, pi.ecn, tx_.data.data(), nwrite); + rv != NETWORK_ERR_OK) { + if (rv != NETWORK_ERR_SEND_BLOCKED) { + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0); + disconnect(); + + return rv; + } + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + on_send_blocked(ep, ps.path.remote, pi.ecn, nwrite); + + return 0; + } + + if (++pktcnt == max_pktcnt) { + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + start_wev_endpoint(ep); + return 0; + } + } +} + +void Client::update_timer() { + auto expiry = ngtcp2_conn_get_expiry(conn_); + auto now = util::timestamp(loop_); + + if (expiry <= now) { + if (!config.quiet) { + auto t = static_cast<ev_tstamp>(now - expiry) / NGTCP2_SECONDS; + std::cerr << "Timer has already expired: " << t << "s" << std::endl; + } + + ev_feed_event(loop_, &timer_, EV_TIMER); + + return; + } + + auto t = static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS; + if (!config.quiet) { + std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat + << std::endl; + } + timer_.repeat = t; + ev_timer_again(loop_, &timer_); +} + +#ifdef HAVE_LINUX_RTNETLINK_H +namespace { +int bind_addr(Address &local_addr, int fd, const in_addr_union *iau, + int family) { + addrinfo hints{}; + addrinfo *res, *rp; + + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + char *node; + std::array<char, NI_MAXHOST> nodebuf; + + if (iau) { + if (inet_ntop(family, iau, nodebuf.data(), nodebuf.size()) == nullptr) { + std::cerr << "inet_ntop: " << strerror(errno) << std::endl; + return -1; + } + + node = nodebuf.data(); + } else { + node = nullptr; + } + + if (auto rv = getaddrinfo(node, "0", &hints, &res); rv != 0) { + std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + for (rp = res; rp; rp = rp->ai_next) { + if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; + } + } + + if (!rp) { + std::cerr << "Could not bind" << std::endl; + return -1; + } + + socklen_t len = sizeof(local_addr.su.storage); + if (getsockname(fd, &local_addr.su.sa, &len) == -1) { + std::cerr << "getsockname: " << strerror(errno) << std::endl; + return -1; + } + local_addr.len = len; + local_addr.ifindex = 0; + + return 0; +} +} // namespace +#endif // HAVE_LINUX_RTNETLINK_H + +#ifndef HAVE_LINUX_RTNETLINK_H +namespace { +int connect_sock(Address &local_addr, int fd, const Address &remote_addr) { + if (connect(fd, &remote_addr.su.sa, remote_addr.len) != 0) { + std::cerr << "connect: " << strerror(errno) << std::endl; + return -1; + } + + socklen_t len = sizeof(local_addr.su.storage); + if (getsockname(fd, &local_addr.su.sa, &len) == -1) { + std::cerr << "getsockname: " << strerror(errno) << std::endl; + return -1; + } + local_addr.len = len; + local_addr.ifindex = 0; + + return 0; +} +} // namespace +#endif // !HAVE_LINUX_RTNETLINK_H + +namespace { +int udp_sock(int family) { + auto fd = util::create_nonblock_socket(family, SOCK_DGRAM, IPPROTO_UDP); + if (fd == -1) { + return -1; + } + + fd_set_recv_ecn(fd, family); + fd_set_ip_mtu_discover(fd, family); + fd_set_ip_dontfrag(fd, family); + + return fd; +} +} // namespace + +namespace { +int create_sock(Address &remote_addr, const char *addr, const char *port) { + addrinfo hints{}; + addrinfo *res, *rp; + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) { + std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + int fd = -1; + + for (rp = res; rp; rp = rp->ai_next) { + fd = udp_sock(rp->ai_family); + if (fd == -1) { + continue; + } + + break; + } + + if (!rp) { + std::cerr << "Could not create socket" << std::endl; + return -1; + } + + remote_addr.len = rp->ai_addrlen; + memcpy(&remote_addr.su, rp->ai_addr, rp->ai_addrlen); + + return fd; +} +} // namespace + +std::optional<Endpoint *> Client::endpoint_for(const Address &remote_addr) { +#ifdef HAVE_LINUX_RTNETLINK_H + in_addr_union iau; + + if (get_local_addr(iau, remote_addr) != 0) { + std::cerr << "Could not get local address for a selected preferred address" + << std::endl; + return nullptr; + } + + auto current_path = ngtcp2_conn_get_path(conn_); + auto current_ep = static_cast<Endpoint *>(current_path->user_data); + if (addreq(¤t_ep->addr.su.sa, iau)) { + return current_ep; + } +#endif // HAVE_LINUX_RTNETLINK_H + + auto fd = udp_sock(remote_addr.su.sa.sa_family); + if (fd == -1) { + return nullptr; + } + + Address local_addr; + +#ifdef HAVE_LINUX_RTNETLINK_H + if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) { + close(fd); + return nullptr; + } +#else // !HAVE_LINUX_RTNETLINK_H + if (connect_sock(local_addr, fd, remote_addr) != 0) { + close(fd); + return nullptr; + } +#endif // !HAVE_LINUX_RTNETLINK_H + + endpoints_.emplace_back(); + auto &ep = endpoints_.back(); + ep.addr = local_addr; + ep.client = this; + ep.fd = fd; + ev_io_init(&ep.rev, readcb, fd, EV_READ); + ep.rev.data = &ep; + + ev_io_start(loop_, &ep.rev); + + return &ep; +} + +void Client::start_change_local_addr_timer() { + ev_timer_start(loop_, &change_local_addr_timer_); +} + +int Client::change_local_addr() { + Address local_addr; + + if (!config.quiet) { + std::cerr << "Changing local address" << std::endl; + } + + auto nfd = udp_sock(remote_addr_.su.sa.sa_family); + if (nfd == -1) { + return -1; + } + +#ifdef HAVE_LINUX_RTNETLINK_H + in_addr_union iau; + + if (get_local_addr(iau, remote_addr_) != 0) { + std::cerr << "Could not get local address" << std::endl; + close(nfd); + return -1; + } + + if (bind_addr(local_addr, nfd, &iau, remote_addr_.su.sa.sa_family) != 0) { + close(nfd); + return -1; + } +#else // !HAVE_LINUX_RTNETLINK_H + if (connect_sock(local_addr, nfd, remote_addr_) != 0) { + close(nfd); + return -1; + } +#endif // !HAVE_LINUX_RTNETLINK_H + + if (!config.quiet) { + std::cerr << "Local address is now " + << util::straddr(&local_addr.su.sa, local_addr.len) << std::endl; + } + + endpoints_.emplace_back(); + auto &ep = endpoints_.back(); + ep.addr = local_addr; + ep.client = this; + ep.fd = nfd; + ev_io_init(&ep.rev, readcb, nfd, EV_READ); + ep.rev.data = &ep; + + ngtcp2_addr addr; + ngtcp2_addr_init(&addr, &local_addr.su.sa, local_addr.len); + + if (config.nat_rebinding) { + ngtcp2_conn_set_local_addr(conn_, &addr); + ngtcp2_conn_set_path_user_data(conn_, &ep); + } else { + auto path = ngtcp2_path{ + addr, + { + const_cast<sockaddr *>(&remote_addr_.su.sa), + remote_addr_.len, + }, + &ep, + }; + if (auto rv = ngtcp2_conn_initiate_immediate_migration( + conn_, &path, util::timestamp(loop_)); + rv != 0) { + std::cerr << "ngtcp2_conn_initiate_immediate_migration: " + << ngtcp2_strerror(rv) << std::endl; + } + } + + ev_io_start(loop_, &ep.rev); + + return 0; +} + +void Client::start_key_update_timer() { + ev_timer_start(loop_, &key_update_timer_); +} + +int Client::update_key(uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen) { + if (!config.quiet) { + std::cerr << "Updating traffic key" << std::endl; + } + + auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_); + auto aead = &crypto_ctx->aead; + auto keylen = ngtcp2_crypto_aead_keylen(aead); + auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + + ++nkey_update_; + + std::array<uint8_t, 64> rx_key, tx_key; + + if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx, + rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(), + tx_iv, current_rx_secret, current_tx_secret, + secretlen) != 0) { + return -1; + } + + if (!config.quiet && config.show_secret) { + std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl; + debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv, + ivlen); + std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl; + debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv, + ivlen); + } + + return 0; +} + +int Client::initiate_key_update() { + if (!config.quiet) { + std::cerr << "Initiate key update" << std::endl; + } + + if (auto rv = ngtcp2_conn_initiate_key_update(conn_, util::timestamp(loop_)); + rv != 0) { + std::cerr << "ngtcp2_conn_initiate_key_update: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +void Client::start_delay_stream_timer() { + ev_timer_start(loop_, &delay_stream_timer_); +} + +int Client::send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr, + unsigned int ecn, const uint8_t *data, size_t datalen) { + if (debug::packet_lost(config.tx_loss_prob)) { + if (!config.quiet) { + std::cerr << "** Simulated outgoing packet loss **" << std::endl; + } + return NETWORK_ERR_OK; + } + + iovec msg_iov; + msg_iov.iov_base = const_cast<uint8_t *>(data); + msg_iov.iov_len = datalen; + + msghdr msg{}; +#ifdef HAVE_LINUX_RTNETLINK_H + msg.msg_name = const_cast<sockaddr *>(remote_addr.addr); + msg.msg_namelen = remote_addr.addrlen; +#endif // HAVE_LINUX_RTNETLINK_H + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + fd_set_ecn(ep.fd, remote_addr.addr->sa_family, ecn); + + ssize_t nwrite = 0; + + do { + nwrite = sendmsg(ep.fd, &msg, 0); + } while (nwrite == -1 && errno == EINTR); + + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return NETWORK_ERR_SEND_BLOCKED; + } + std::cerr << "sendmsg: " << strerror(errno) << std::endl; + if (errno == EMSGSIZE) { + return 0; + } + return NETWORK_ERR_FATAL; + } + + assert(static_cast<size_t>(nwrite) == datalen); + + if (!config.quiet) { + std::cerr << "Sent packet: local=" + << util::straddr(&ep.addr.su.sa, ep.addr.len) << " remote=" + << util::straddr(remote_addr.addr, remote_addr.addrlen) + << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite + << " bytes" << std::endl; + } + + return NETWORK_ERR_OK; +} + +void Client::on_send_blocked(const Endpoint &ep, const ngtcp2_addr &remote_addr, + unsigned int ecn, size_t datalen) { + assert(!tx_.send_blocked); + + tx_.send_blocked = true; + + memcpy(&tx_.blocked.remote_addr.su, remote_addr.addr, remote_addr.addrlen); + tx_.blocked.remote_addr.len = remote_addr.addrlen; + tx_.blocked.ecn = ecn; + tx_.blocked.datalen = datalen; + tx_.blocked.endpoint = &ep; + + start_wev_endpoint(ep); +} + +void Client::start_wev_endpoint(const Endpoint &ep) { + // We do not close ep.fd, so we can expect that each Endpoint has + // unique fd. + if (ep.fd != wev_.fd) { + if (ev_is_active(&wev_)) { + ev_io_stop(loop_, &wev_); + } + + ev_io_set(&wev_, ep.fd, EV_WRITE); + } + + ev_io_start(loop_, &wev_); +} + +int Client::send_blocked_packet() { + assert(tx_.send_blocked); + + ngtcp2_addr remote_addr{ + .addr = &tx_.blocked.remote_addr.su.sa, + .addrlen = tx_.blocked.remote_addr.len, + }; + + auto rv = send_packet(*tx_.blocked.endpoint, remote_addr, tx_.blocked.ecn, + tx_.data.data(), tx_.blocked.datalen); + if (rv != 0) { + if (rv == NETWORK_ERR_SEND_BLOCKED) { + assert(wev_.fd == tx_.blocked.endpoint->fd); + + ev_io_start(loop_, &wev_); + + return 0; + } + + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0); + disconnect(); + + return rv; + } + + tx_.send_blocked = false; + + return 0; +} + +int Client::handle_error() { + if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_) || + ngtcp2_conn_is_in_draining_period(conn_)) { + return 0; + } + + std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf; + + ngtcp2_path_storage ps; + + ngtcp2_path_storage_zero(&ps); + + ngtcp2_pkt_info pi; + + auto nwrite = ngtcp2_conn_write_connection_close( + conn_, &ps.path, &pi, buf.data(), buf.size(), &last_error_, + util::timestamp(loop_)); + if (nwrite < 0) { + std::cerr << "ngtcp2_conn_write_connection_close: " + << ngtcp2_strerror(nwrite) << std::endl; + return -1; + } + + if (nwrite == 0) { + return 0; + } + + return send_packet(*static_cast<Endpoint *>(ps.path.user_data), + ps.path.remote, pi.ecn, buf.data(), nwrite); +} + +int Client::on_stream_close(int64_t stream_id, uint64_t app_error_code) { + if (httpconn_) { + if (app_error_code == 0) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code); + switch (rv) { + case 0: + break; + case NGHTTP3_ERR_STREAM_NOT_FOUND: + // We have to handle the case when stream opened but no data is + // transferred. In this case, nghttp3_conn_close_stream might + // return error. + if (!ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(conn_, stream_id)); + ngtcp2_conn_extend_max_streams_uni(conn_, 1); + } + break; + default: + std::cerr << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0); + return -1; + } + } + + return 0; +} + +int Client::on_stream_reset(int64_t stream_id) { + if (httpconn_) { + if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); + rv != 0) { + std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + } + return 0; +} + +int Client::on_stream_stop_sending(int64_t stream_id) { + if (!httpconn_) { + return 0; + } + + if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); + rv != 0) { + std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +int Client::make_stream_early() { + if (setup_httpconn() != 0) { + return -1; + } + + return on_extend_max_streams(); +} + +int Client::on_extend_max_streams() { + int64_t stream_id; + + if ((config.delay_stream && !handshake_confirmed_) || + ev_is_active(&delay_stream_timer_)) { + return 0; + } + + for (; nstreams_done_ < config.nstreams; ++nstreams_done_) { + if (auto rv = ngtcp2_conn_open_bidi_stream(conn_, &stream_id, nullptr); + rv != 0) { + assert(NGTCP2_ERR_STREAM_ID_BLOCKED == rv); + break; + } + + auto stream = std::make_unique<Stream>( + config.requests[nstreams_done_ % config.requests.size()], stream_id); + + if (submit_http_request(stream.get()) != 0) { + break; + } + + if (!config.download.empty()) { + stream->open_file(stream->req.path); + } + streams_.emplace(stream_id, std::move(stream)); + } + return 0; +} + +namespace { +nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec, + size_t veccnt, uint32_t *pflags, void *user_data, + void *stream_user_data) { + vec[0].base = config.data; + vec[0].len = config.datalen; + *pflags |= NGHTTP3_DATA_FLAG_EOF; + + return 1; +} +} // namespace + +int Client::submit_http_request(const Stream *stream) { + std::string content_length_str; + + const auto &req = stream->req; + + std::array<nghttp3_nv, 6> nva{ + util::make_nv_nn(":method", config.http_method), + util::make_nv_nn(":scheme", req.scheme), + util::make_nv_nn(":authority", req.authority), + util::make_nv_nn(":path", req.path), + util::make_nv_nn("user-agent", "nghttp3/ngtcp2 client"), + }; + size_t nvlen = 5; + if (config.fd != -1) { + content_length_str = util::format_uint(config.datalen); + nva[nvlen++] = util::make_nv_nc("content-length", content_length_str); + } + + if (!config.quiet) { + debug::print_http_request_headers(stream->stream_id, nva.data(), nvlen); + } + + nghttp3_data_reader dr{}; + dr.read_data = read_data; + + if (auto rv = nghttp3_conn_submit_request( + httpconn_, stream->stream_id, nva.data(), nvlen, + config.fd == -1 ? nullptr : &dr, nullptr); + rv != 0) { + std::cerr << "nghttp3_conn_submit_request: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +int Client::recv_stream_data(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + auto nconsumed = nghttp3_conn_read_stream( + httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN); + if (nconsumed < 0) { + std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr, + 0); + return -1; + } + + ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(conn_, nconsumed); + + return 0; +} + +int Client::acked_stream_data_offset(int64_t stream_id, uint64_t datalen) { + if (auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen); + rv != 0) { + std::cerr << "nghttp3_conn_add_ack_offset: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +int Client::select_preferred_address(Address &selected_addr, + const ngtcp2_preferred_addr *paddr) { + auto path = ngtcp2_conn_get_path(conn_); + + switch (path->local.addr->sa_family) { + case AF_INET: + if (!paddr->ipv4_present) { + return -1; + } + selected_addr.su.in = paddr->ipv4; + selected_addr.len = sizeof(paddr->ipv4); + break; + case AF_INET6: + if (!paddr->ipv6_present) { + return -1; + } + selected_addr.su.in6 = paddr->ipv6; + selected_addr.len = sizeof(paddr->ipv6); + break; + default: + return -1; + } + + char host[NI_MAXHOST], service[NI_MAXSERV]; + if (auto rv = getnameinfo(&selected_addr.su.sa, selected_addr.len, host, + sizeof(host), service, sizeof(service), + NI_NUMERICHOST | NI_NUMERICSERV); + rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + if (!config.quiet) { + std::cerr << "selected server preferred_address is [" << host + << "]:" << service << std::endl; + } + + return 0; +} + +namespace { +int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, + size_t datalen, void *user_data, void *stream_user_data) { + if (!config.quiet && !config.no_http_dump) { + debug::print_http_data(stream_id, data, datalen); + } + auto c = static_cast<Client *>(user_data); + c->http_consume(stream_id, datalen); + c->http_write_data(stream_id, data, datalen); + return 0; +} +} // namespace + +namespace { +int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t nconsumed, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + c->http_consume(stream_id, nconsumed); + return 0; +} +} // namespace + +void Client::http_consume(int64_t stream_id, size_t nconsumed) { + ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(conn_, nconsumed); +} + +void Client::http_write_data(int64_t stream_id, const uint8_t *data, + size_t datalen) { + auto it = streams_.find(stream_id); + if (it == std::end(streams_)) { + return; + } + + auto &stream = (*it).second; + + if (stream->fd == -1) { + return; + } + + ssize_t nwrite; + do { + nwrite = write(stream->fd, data, datalen); + } while (nwrite == -1 && errno == EINTR); +} + +namespace { +int http_begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + if (!config.quiet) { + debug::print_http_begin_response_headers(stream_id); + } + return 0; +} +} // namespace + +namespace { +int http_recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) { + if (!config.quiet) { + debug::print_http_header(stream_id, name, value, flags); + } + return 0; +} +} // namespace + +namespace { +int http_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, + void *user_data, void *stream_user_data) { + if (!config.quiet) { + debug::print_http_end_headers(stream_id); + } + return 0; +} +} // namespace + +namespace { +int http_begin_trailers(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + if (!config.quiet) { + debug::print_http_begin_trailers(stream_id); + } + return 0; +} +} // namespace + +namespace { +int http_recv_trailer(nghttp3_conn *conn, int64_t stream_id, int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) { + if (!config.quiet) { + debug::print_http_header(stream_id, name, value, flags); + } + return 0; +} +} // namespace + +namespace { +int http_end_trailers(nghttp3_conn *conn, int64_t stream_id, int fin, + void *user_data, void *stream_user_data) { + if (!config.quiet) { + debug::print_http_end_trailers(stream_id); + } + return 0; +} +} // namespace + +namespace { +int http_stop_sending(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + if (c->stop_sending(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::stop_sending(int64_t stream_id, uint64_t app_error_code) { + if (auto rv = + ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code); + rv != 0) { + std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + return 0; +} + +namespace { +int http_reset_stream(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + if (c->reset_stream(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::reset_stream(int64_t stream_id, uint64_t app_error_code) { + if (auto rv = + ngtcp2_conn_shutdown_stream_write(conn_, stream_id, app_error_code); + rv != 0) { + std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + return 0; +} + +namespace { +int http_stream_close(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *conn_user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(conn_user_data); + if (c->http_stream_close(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::http_stream_close(int64_t stream_id, uint64_t app_error_code) { + if (ngtcp2_is_bidi_stream(stream_id)) { + assert(ngtcp2_conn_is_local_stream(conn_, stream_id)); + + ++nstreams_closed_; + + if (config.exit_on_first_stream_close || + (config.exit_on_all_streams_close && + config.nstreams == nstreams_done_ && + nstreams_closed_ == nstreams_done_)) { + if (handshake_confirmed_) { + should_exit_ = true; + } else { + should_exit_on_handshake_confirmed_ = true; + } + } + } else { + assert(!ngtcp2_conn_is_local_stream(conn_, stream_id)); + ngtcp2_conn_extend_max_streams_uni(conn_, 1); + } + + if (auto it = streams_.find(stream_id); it != std::end(streams_)) { + if (!config.quiet) { + std::cerr << "HTTP stream " << stream_id << " closed with error code " + << app_error_code << std::endl; + } + streams_.erase(it); + } + + return 0; +} + +int Client::setup_httpconn() { + if (httpconn_) { + return 0; + } + + if (ngtcp2_conn_get_max_local_streams_uni(conn_) < 3) { + std::cerr << "peer does not allow at least 3 unidirectional streams." + << std::endl; + return -1; + } + + nghttp3_callbacks callbacks{ + nullptr, // acked_stream_data + ::http_stream_close, + ::http_recv_data, + ::http_deferred_consume, + ::http_begin_headers, + ::http_recv_header, + ::http_end_headers, + ::http_begin_trailers, + ::http_recv_trailer, + ::http_end_trailers, + ::http_stop_sending, + nullptr, // end_stream + ::http_reset_stream, + nullptr, // shutdown + }; + nghttp3_settings settings; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4_k; + settings.qpack_blocked_streams = 100; + + auto mem = nghttp3_mem_default(); + + if (auto rv = + nghttp3_conn_client_new(&httpconn_, &callbacks, &settings, mem, this); + rv != 0) { + std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + int64_t ctrl_stream_id; + + if (auto rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr); + rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + if (auto rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id); + rv != 0) { + std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + if (!config.quiet) { + fprintf(stderr, "http: control stream=%" PRIx64 "\n", ctrl_stream_id); + } + + int64_t qpack_enc_stream_id, qpack_dec_stream_id; + + if (auto rv = + ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr); + rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + if (auto rv = + ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr); + rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + if (auto rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id, + qpack_dec_stream_id); + rv != 0) { + std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + if (!config.quiet) { + fprintf(stderr, + "http: QPACK streams encoder=%" PRIx64 " decoder=%" PRIx64 "\n", + qpack_enc_stream_id, qpack_dec_stream_id); + } + + return 0; +} + +const std::vector<uint32_t> &Client::get_offered_versions() const { + return offered_versions_; +} + +bool Client::get_early_data() const { return early_data_; }; + +namespace { +int run(Client &c, const char *addr, const char *port, + TLSClientContext &tls_ctx) { + Address remote_addr, local_addr; + + auto fd = create_sock(remote_addr, addr, port); + if (fd == -1) { + return -1; + } + +#ifdef HAVE_LINUX_RTNETLINK_H + in_addr_union iau; + + if (get_local_addr(iau, remote_addr) != 0) { + std::cerr << "Could not get local address" << std::endl; + close(fd); + return -1; + } + + if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) { + close(fd); + return -1; + } +#else // !HAVE_LINUX_RTNETLINK_H + if (connect_sock(local_addr, fd, remote_addr) != 0) { + close(fd); + return -1; + } +#endif // !HAVE_LINUX_RTNETLINK_H + + if (c.init(fd, local_addr, remote_addr, addr, port, tls_ctx) != 0) { + return -1; + } + + // TODO Do we need this ? + if (auto rv = c.on_write(); rv != 0) { + return rv; + } + + ev_run(EV_DEFAULT, 0); + + return 0; +} +} // namespace + +namespace { +std::string_view get_string(const char *uri, const http_parser_url &u, + http_parser_url_fields f) { + auto p = &u.field_data[f]; + return {uri + p->off, p->len}; +} +} // namespace + +namespace { +int parse_uri(Request &req, const char *uri) { + http_parser_url u; + + http_parser_url_init(&u); + if (http_parser_parse_url(uri, strlen(uri), /* is_connect = */ 0, &u) != 0) { + return -1; + } + + if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) { + return -1; + } + + req.scheme = get_string(uri, u, UF_SCHEMA); + + req.authority = get_string(uri, u, UF_HOST); + if (util::numeric_host(req.authority.c_str(), AF_INET6)) { + req.authority = '[' + req.authority + ']'; + } + if (u.field_set & (1 << UF_PORT)) { + req.authority += ':'; + req.authority += get_string(uri, u, UF_PORT); + } + + if (u.field_set & (1 << UF_PATH)) { + req.path = get_string(uri, u, UF_PATH); + } else { + req.path = "/"; + } + + if (u.field_set & (1 << UF_QUERY)) { + req.path += '?'; + req.path += get_string(uri, u, UF_QUERY); + } + + return 0; +} +} // namespace + +namespace { +int parse_requests(char **argv, size_t argvlen) { + for (size_t i = 0; i < argvlen; ++i) { + auto uri = argv[i]; + Request req; + if (parse_uri(req, uri) != 0) { + std::cerr << "Could not parse URI: " << uri << std::endl; + return -1; + } + config.requests.emplace_back(std::move(req)); + } + return 0; +} +} // namespace + +std::ofstream keylog_file; + +namespace { +void print_usage() { + std::cerr << "Usage: client [OPTIONS] <HOST> <PORT> [<URI>...]" << std::endl; +} +} // namespace + +namespace { +void config_set_default(Config &config) { + config = Config{}; + config.tx_loss_prob = 0.; + config.rx_loss_prob = 0.; + config.fd = -1; + config.ciphers = util::crypto_default_ciphers(); + config.groups = util::crypto_default_groups(); + config.nstreams = 0; + config.data = nullptr; + config.datalen = 0; + config.version = NGTCP2_PROTO_VER_V1; + config.timeout = 30 * NGTCP2_SECONDS; + config.http_method = "GET"sv; + config.max_data = 15_m; + config.max_stream_data_bidi_local = 6_m; + config.max_stream_data_bidi_remote = 6_m; + config.max_stream_data_uni = 6_m; + config.max_window = 24_m; + config.max_stream_window = 16_m; + config.max_streams_uni = 100; + config.cc_algo = NGTCP2_CC_ALGO_CUBIC; + config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; + config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT; + config.ack_thresh = 2; +} +} // namespace + +namespace { +void print_help() { + print_usage(); + + config_set_default(config); + + std::cout << R"( + <HOST> Remote server host (DNS name or IP address). In case of + DNS name, it will be sent in TLS SNI extension. + <PORT> Remote server port + <URI> Remote URI +Options: + -t, --tx-loss=<P> + The probability of losing outgoing packets. <P> must be + [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 + means 100% packet loss. + -r, --rx-loss=<P> + The probability of losing incoming packets. <P> must be + [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 + means 100% packet loss. + -d, --data=<PATH> + Read data from <PATH>, and send them as STREAM data. + -n, --nstreams=<N> + The number of requests. <URI>s are used in the order of + appearance in the command-line. If the number of <URI> + list is less than <N>, <URI> list is wrapped. It + defaults to 0 which means the number of <URI> specified. + -v, --version=<HEX> + Specify QUIC version to use in hex string. If the given + version is not supported by libngtcp2, client will use + QUIC v1 long packet types. Instead of specifying hex + string, there are special aliases available: "v1" + indicates QUIC v1, and "v2draft" indicates QUIC v2 + draft. + Default: )" + << std::hex << "0x" << config.version << std::dec << R"( + --preferred-versions=<HEX>[[,<HEX>]...] + Specify QUIC versions in hex string in the order of + preference. Client chooses one of those versions if + client received Version Negotiation packet from server. + These versions must be supported by libngtcp2. Instead + of specifying hex string, there are special aliases + available: "v1" indicates QUIC v1, and "v2draft" + indicates QUIC v2 draft. + --other-versions=<HEX>[[,<HEX>]...] + Specify QUIC versions in hex string that are sent in + other_versions field of version_information transport + parameter. This list can include a version which is not + supported by libngtcp2. Instead of specifying hex + string, there are special aliases available: "v1" + indicates QUIC v1, and "v2draft" indicates QUIC v2 + draft. + -q, --quiet Suppress debug output. + -s, --show-secret + Print out secrets unless --quiet is used. + --timeout=<DURATION> + Specify idle timeout. + Default: )" + << util::format_duration(config.timeout) << R"( + --ciphers=<CIPHERS> + Specify the cipher suite list to enable. + Default: )" + << config.ciphers << R"( + --groups=<GROUPS> + Specify the supported groups. + Default: )" + << config.groups << R"( + --session-file=<PATH> + Read/write TLS session from/to <PATH>. To resume a + session, the previous session must be supplied with this + option. + --tp-file=<PATH> + Read/write QUIC transport parameters from/to <PATH>. To + send 0-RTT data, the transport parameters received from + the previous session must be supplied with this option. + --dcid=<DCID> + Specify initial DCID. <DCID> is hex string. When + decoded as binary, it should be at least 8 bytes and at + most 18 bytes long. + --scid=<SCID> + Specify source connection ID. <SCID> is hex string. If + an empty string is given, zero length connection ID is + assumed. + --change-local-addr=<DURATION> + Client changes local address when <DURATION> elapse + after handshake completes. + --nat-rebinding + When used with --change-local-addr, simulate NAT + rebinding. In other words, client changes local + address, but it does not start path validation. + --key-update=<DURATION> + Client initiates key update when <DURATION> elapse after + handshake completes. + -m, --http-method=<METHOD> + Specify HTTP method. Default: )" + << config.http_method << R"( + --delay-stream=<DURATION> + Delay sending STREAM data in 1-RTT for <DURATION> after + handshake completes. + --no-preferred-addr + Do not try to use preferred address offered by server. + --key=<PATH> + The path to client private key PEM file. + --cert=<PATH> + The path to client certificate PEM file. + --download=<PATH> + The path to the directory to save a downloaded content. + It is undefined if 2 concurrent requests write to the + same file. If a request path does not contain a path + component usable as a file name, it defaults to + "index.html". + --no-quic-dump + Disables printing QUIC STREAM and CRYPTO frame data out. + --no-http-dump + Disables printing HTTP response body out. + --qlog-file=<PATH> + The path to write qlog. This option and --qlog-dir are + mutually exclusive. + --qlog-dir=<PATH> + Path to the directory where qlog file is stored. The + file name of each qlog is the Source Connection ID of + client. This option and --qlog-file are mutually + exclusive. + --max-data=<SIZE> + The initial connection-level flow control window. + Default: )" + << util::format_uint_iec(config.max_data) << R"( + --max-stream-data-bidi-local=<SIZE> + The initial stream-level flow control window for a + bidirectional stream that the local endpoint initiates. + Default: )" + << util::format_uint_iec(config.max_stream_data_bidi_local) << R"( + --max-stream-data-bidi-remote=<SIZE> + The initial stream-level flow control window for a + bidirectional stream that the remote endpoint initiates. + Default: )" + << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"( + --max-stream-data-uni=<SIZE> + The initial stream-level flow control window for a + unidirectional stream. + Default: )" + << util::format_uint_iec(config.max_stream_data_uni) << R"( + --max-streams-bidi=<N> + The number of the concurrent bidirectional streams. + Default: )" + << config.max_streams_bidi << R"( + --max-streams-uni=<N> + The number of the concurrent unidirectional streams. + Default: )" + << config.max_streams_uni << R"( + --exit-on-first-stream-close + Exit when a first client initialted HTTP stream is + closed. + --exit-on-all-streams-close + Exit when all client initiated HTTP streams are closed. + --disable-early-data + Disable early data. + --cc=(cubic|reno|bbr|bbr2) + The name of congestion controller algorithm. + Default: )" + << util::strccalgo(config.cc_algo) << R"( + --token-file=<PATH> + Read/write token from/to <PATH>. Token is obtained from + NEW_TOKEN frame from server. + --sni=<DNSNAME> + Send <DNSNAME> in TLS SNI, overriding the DNS name + specified in <HOST>. + --initial-rtt=<DURATION> + Set an initial RTT. + Default: )" + << util::format_duration(config.initial_rtt) << R"( + --max-window=<SIZE> + Maximum connection-level flow control window size. The + window auto-tuning is enabled if nonzero value is given, + and window size is scaled up to this value. + Default: )" + << util::format_uint_iec(config.max_window) << R"( + --max-stream-window=<SIZE> + Maximum stream-level flow control window size. The + window auto-tuning is enabled if nonzero value is given, + and window size is scaled up to this value. + Default: )" + << util::format_uint_iec(config.max_stream_window) << R"( + --max-udp-payload-size=<SIZE> + Override maximum UDP payload size that client transmits. + --handshake-timeout=<DURATION> + Set the QUIC handshake timeout. + Default: )" + << util::format_duration(config.handshake_timeout) << R"( + --no-pmtud Disables Path MTU Discovery. + --ack-thresh=<N> + The minimum number of the received ACK eliciting packets + that triggers immediate acknowledgement. + Default: )" + << config.ack_thresh << R"( + -h, --help Display this help and exit. + +--- + + The <SIZE> argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The <DURATION> argument is an integer and an optional unit (e.g., 1s + is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms, + us, or ns (hours, minutes, seconds, milliseconds, microseconds, and + nanoseconds respectively). If a unit is omitted, a second is used + as unit. + + The <HEX> argument is an hex string which must start with "0x" + (e.g., 0x00000001).)" + << std::endl; +} +} // namespace + +int main(int argc, char **argv) { + config_set_default(config); + char *data_path = nullptr; + const char *private_key_file = nullptr; + const char *cert_file = nullptr; + + for (;;) { + static int flag = 0; + constexpr static option long_opts[] = { + {"help", no_argument, nullptr, 'h'}, + {"tx-loss", required_argument, nullptr, 't'}, + {"rx-loss", required_argument, nullptr, 'r'}, + {"data", required_argument, nullptr, 'd'}, + {"http-method", required_argument, nullptr, 'm'}, + {"nstreams", required_argument, nullptr, 'n'}, + {"version", required_argument, nullptr, 'v'}, + {"quiet", no_argument, nullptr, 'q'}, + {"show-secret", no_argument, nullptr, 's'}, + {"ciphers", required_argument, &flag, 1}, + {"groups", required_argument, &flag, 2}, + {"timeout", required_argument, &flag, 3}, + {"session-file", required_argument, &flag, 4}, + {"tp-file", required_argument, &flag, 5}, + {"dcid", required_argument, &flag, 6}, + {"change-local-addr", required_argument, &flag, 7}, + {"key-update", required_argument, &flag, 8}, + {"nat-rebinding", no_argument, &flag, 9}, + {"delay-stream", required_argument, &flag, 10}, + {"no-preferred-addr", no_argument, &flag, 11}, + {"key", required_argument, &flag, 12}, + {"cert", required_argument, &flag, 13}, + {"download", required_argument, &flag, 14}, + {"no-quic-dump", no_argument, &flag, 15}, + {"no-http-dump", no_argument, &flag, 16}, + {"qlog-file", required_argument, &flag, 17}, + {"max-data", required_argument, &flag, 18}, + {"max-stream-data-bidi-local", required_argument, &flag, 19}, + {"max-stream-data-bidi-remote", required_argument, &flag, 20}, + {"max-stream-data-uni", required_argument, &flag, 21}, + {"max-streams-bidi", required_argument, &flag, 22}, + {"max-streams-uni", required_argument, &flag, 23}, + {"exit-on-first-stream-close", no_argument, &flag, 24}, + {"disable-early-data", no_argument, &flag, 25}, + {"qlog-dir", required_argument, &flag, 26}, + {"cc", required_argument, &flag, 27}, + {"exit-on-all-streams-close", no_argument, &flag, 28}, + {"token-file", required_argument, &flag, 29}, + {"sni", required_argument, &flag, 30}, + {"initial-rtt", required_argument, &flag, 31}, + {"max-window", required_argument, &flag, 32}, + {"max-stream-window", required_argument, &flag, 33}, + {"scid", required_argument, &flag, 34}, + {"max-udp-payload-size", required_argument, &flag, 35}, + {"handshake-timeout", required_argument, &flag, 36}, + {"other-versions", required_argument, &flag, 37}, + {"no-pmtud", no_argument, &flag, 38}, + {"preferred-versions", required_argument, &flag, 39}, + {"ack-thresh", required_argument, &flag, 40}, + {nullptr, 0, nullptr, 0}, + }; + + auto optidx = 0; + auto c = getopt_long(argc, argv, "d:him:n:qr:st:v:", long_opts, &optidx); + if (c == -1) { + break; + } + switch (c) { + case 'd': + // --data + data_path = optarg; + break; + case 'h': + // --help + print_help(); + exit(EXIT_SUCCESS); + case 'm': + // --http-method + config.http_method = optarg; + break; + case 'n': + // --streams + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "streams: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > NGTCP2_MAX_VARINT) { + std::cerr << "streams: must not exceed " << NGTCP2_MAX_VARINT + << std::endl; + exit(EXIT_FAILURE); + } else { + config.nstreams = *n; + } + break; + case 'q': + // --quiet + config.quiet = true; + break; + case 'r': + // --rx-loss + config.rx_loss_prob = strtod(optarg, nullptr); + break; + case 's': + // --show-secret + config.show_secret = true; + break; + case 't': + // --tx-loss + config.tx_loss_prob = strtod(optarg, nullptr); + break; + case 'v': { + // --version + if (optarg == "v1"sv) { + config.version = NGTCP2_PROTO_VER_V1; + break; + } + if (optarg == "v2draft"sv) { + config.version = NGTCP2_PROTO_VER_V2_DRAFT; + break; + } + auto rv = util::parse_version(optarg); + if (!rv) { + std::cerr << "version: invalid version " << std::quoted(optarg) + << std::endl; + exit(EXIT_FAILURE); + } + config.version = *rv; + break; + } + case '?': + print_usage(); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // --ciphers + config.ciphers = optarg; + break; + case 2: + // --groups + config.groups = optarg; + break; + case 3: + // --timeout + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "timeout: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.timeout = *t; + } + break; + case 4: + // --session-file + config.session_file = optarg; + break; + case 5: + // --tp-file + config.tp_file = optarg; + break; + case 6: { + // --dcid + auto dcidlen2 = strlen(optarg); + if (dcidlen2 % 2 || dcidlen2 / 2 < 8 || dcidlen2 / 2 > 18) { + std::cerr << "dcid: wrong length" << std::endl; + exit(EXIT_FAILURE); + } + auto dcid = util::decode_hex(optarg); + ngtcp2_cid_init(&config.dcid, + reinterpret_cast<const uint8_t *>(dcid.c_str()), + dcid.size()); + break; + } + case 7: + // --change-local-addr + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "change-local-addr: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.change_local_addr = *t; + } + break; + case 8: + // --key-update + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "key-update: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.key_update = *t; + } + break; + case 9: + // --nat-rebinding + config.nat_rebinding = true; + break; + case 10: + // --delay-stream + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "delay-stream: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.delay_stream = *t; + } + break; + case 11: + // --no-preferred-addr + config.no_preferred_addr = true; + break; + case 12: + // --key + private_key_file = optarg; + break; + case 13: + // --cert + cert_file = optarg; + break; + case 14: + // --download + config.download = optarg; + break; + case 15: + // --no-quic-dump + config.no_quic_dump = true; + break; + case 16: + // --no-http-dump + config.no_http_dump = true; + break; + case 17: + // --qlog-file + config.qlog_file = optarg; + break; + case 18: + // --max-data + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-data: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_data = *n; + } + break; + case 19: + // --max-stream-data-bidi-local + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-bidi-local: invalid argument" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_bidi_local = *n; + } + break; + case 20: + // --max-stream-data-bidi-remote + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-bidi-remote: invalid argument" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_bidi_remote = *n; + } + break; + case 21: + // --max-stream-data-uni + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-uni: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_uni = *n; + } + break; + case 22: + // --max-streams-bidi + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-streams-bidi: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_streams_bidi = *n; + } + break; + case 23: + // --max-streams-uni + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-streams-uni: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_streams_uni = *n; + } + break; + case 24: + // --exit-on-first-stream-close + config.exit_on_first_stream_close = true; + break; + case 25: + // --disable-early-data + config.disable_early_data = true; + break; + case 26: + // --qlog-dir + config.qlog_dir = optarg; + break; + case 27: + // --cc + if (strcmp("cubic", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_CUBIC; + break; + } + if (strcmp("reno", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_RENO; + break; + } + if (strcmp("bbr", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_BBR; + break; + } + if (strcmp("bbr2", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_BBR2; + break; + } + std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl; + exit(EXIT_FAILURE); + case 28: + // --exit-on-all-streams-close + config.exit_on_all_streams_close = true; + break; + case 29: + // --token-file + config.token_file = optarg; + break; + case 30: + // --sni + config.sni = optarg; + break; + case 31: + // --initial-rtt + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "initial-rtt: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.initial_rtt = *t; + } + break; + case 32: + // --max-window + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-window: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_window = *n; + } + break; + case 33: + // --max-stream-window + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-window: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_window = *n; + } + break; + case 34: { + // --scid + auto scid = util::decode_hex(optarg); + ngtcp2_cid_init(&config.scid, + reinterpret_cast<const uint8_t *>(scid.c_str()), + scid.size()); + config.scid_present = true; + break; + } + case 35: + // --max-udp-payload-size + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-udp-payload-size: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 64_k) { + std::cerr << "max-udp-payload-size: must not exceed 65536" + << std::endl; + exit(EXIT_FAILURE); + } else if (*n == 0) { + std::cerr << "max-udp-payload-size: must not be 0" << std::endl; + } else { + config.max_udp_payload_size = *n; + } + break; + case 36: + // --handshake-timeout + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "handshake-timeout: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.handshake_timeout = *t; + } + break; + case 37: { + // --other-versions + if (strlen(optarg) == 0) { + config.other_versions.resize(0); + break; + } + auto l = util::split_str(optarg); + config.other_versions.resize(l.size()); + auto it = std::begin(config.other_versions); + for (const auto &k : l) { + if (k == "v1"sv) { + *it++ = NGTCP2_PROTO_VER_V1; + continue; + } + if (k == "v2draft"sv) { + *it++ = NGTCP2_PROTO_VER_V2_DRAFT; + continue; + } + auto rv = util::parse_version(k); + if (!rv) { + std::cerr << "other-versions: invalid version " << std::quoted(k) + << std::endl; + exit(EXIT_FAILURE); + } + *it++ = *rv; + } + break; + } + case 38: + // --no-pmtud + config.no_pmtud = true; + break; + case 39: { + // --preferred-versions + auto l = util::split_str(optarg); + if (l.size() > max_preferred_versionslen) { + std::cerr << "preferred-versions: too many versions > " + << max_preferred_versionslen << std::endl; + } + config.preferred_versions.resize(l.size()); + auto it = std::begin(config.preferred_versions); + for (const auto &k : l) { + if (k == "v1"sv) { + *it++ = NGTCP2_PROTO_VER_V1; + continue; + } + if (k == "v2draft"sv) { + *it++ = NGTCP2_PROTO_VER_V2_DRAFT; + continue; + } + auto rv = util::parse_version(k); + if (!rv) { + std::cerr << "preferred-versions: invalid version " + << std::quoted(k) << std::endl; + exit(EXIT_FAILURE); + } + if (!ngtcp2_is_supported_version(*rv)) { + std::cerr << "preferred-versions: unsupported version " + << std::quoted(k) << std::endl; + exit(EXIT_FAILURE); + } + *it++ = *rv; + } + break; + } + case 40: + // --ack-thresh + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "ack-thresh: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 100) { + std::cerr << "ack-thresh: must not exceed 100" << std::endl; + exit(EXIT_FAILURE); + } else { + config.ack_thresh = *n; + } + break; + } + break; + default: + break; + }; + } + + if (argc - optind < 2) { + std::cerr << "Too few arguments" << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + if (!config.qlog_file.empty() && !config.qlog_dir.empty()) { + std::cerr << "qlog-file and qlog-dir are mutually exclusive" << std::endl; + exit(EXIT_FAILURE); + } + + if (config.exit_on_first_stream_close && config.exit_on_all_streams_close) { + std::cerr << "exit-on-first-stream-close and exit-on-all-streams-close are " + "mutually exclusive" + << std::endl; + exit(EXIT_FAILURE); + } + + if (data_path) { + auto fd = open(data_path, O_RDONLY); + if (fd == -1) { + std::cerr << "data: Could not open file " << data_path << ": " + << strerror(errno) << std::endl; + exit(EXIT_FAILURE); + } + struct stat st; + if (fstat(fd, &st) != 0) { + std::cerr << "data: Could not stat file " << data_path << ": " + << strerror(errno) << std::endl; + exit(EXIT_FAILURE); + } + config.fd = fd; + config.datalen = st.st_size; + auto addr = mmap(nullptr, config.datalen, PROT_READ, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + std::cerr << "data: Could not mmap file " << data_path << ": " + << strerror(errno) << std::endl; + exit(EXIT_FAILURE); + } + config.data = static_cast<uint8_t *>(addr); + } + + auto addr = argv[optind++]; + auto port = argv[optind++]; + + if (parse_requests(&argv[optind], argc - optind) != 0) { + exit(EXIT_FAILURE); + } + + if (!ngtcp2_is_reserved_version(config.version)) { + if (!config.preferred_versions.empty() && + std::find(std::begin(config.preferred_versions), + std::end(config.preferred_versions), + config.version) == std::end(config.preferred_versions)) { + std::cerr << "preferred-version: must include version " + << "0x" << config.version << std::endl; + exit(EXIT_FAILURE); + } + + if (!config.other_versions.empty() && + std::find(std::begin(config.other_versions), + std::end(config.other_versions), + config.version) == std::end(config.other_versions)) { + std::cerr << "other-versions: must include version " + << "0x" << config.version << std::endl; + exit(EXIT_FAILURE); + } + } + + if (config.nstreams == 0) { + config.nstreams = config.requests.size(); + } + + TLSClientContext tls_ctx; + if (tls_ctx.init(private_key_file, cert_file) != 0) { + exit(EXIT_FAILURE); + } + + auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT); + + auto keylog_filename = getenv("SSLKEYLOGFILE"); + if (keylog_filename) { + keylog_file.open(keylog_filename, std::ios_base::app); + if (keylog_file) { + tls_ctx.enable_keylog(); + } + } + + if (util::generate_secret(config.static_secret.data(), + config.static_secret.size()) != 0) { + std::cerr << "Unable to generate static secret" << std::endl; + exit(EXIT_FAILURE); + } + + auto client_chosen_version = config.version; + + for (;;) { + Client c(EV_DEFAULT, client_chosen_version, config.version); + + if (run(c, addr, port, tls_ctx) != 0) { + exit(EXIT_FAILURE); + } + + if (config.preferred_versions.empty()) { + break; + } + + auto &offered_versions = c.get_offered_versions(); + if (offered_versions.empty()) { + break; + } + + client_chosen_version = ngtcp2_select_version( + config.preferred_versions.data(), config.preferred_versions.size(), + offered_versions.data(), offered_versions.size()); + + if (client_chosen_version == 0) { + std::cerr << "Unable to select a version" << std::endl; + exit(EXIT_FAILURE); + } + + if (!config.quiet) { + std::cerr << "Client selected version " << std::hex << "0x" + << client_chosen_version << std::dec << std::endl; + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/client.h b/examples/client.h new file mode 100644 index 0000000..d861917 --- /dev/null +++ b/examples/client.h @@ -0,0 +1,192 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef CLIENT_H +#define CLIENT_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <vector> +#include <deque> +#include <map> +#include <string_view> +#include <memory> + +#include <ngtcp2/ngtcp2.h> +#include <ngtcp2/ngtcp2_crypto.h> +#include <nghttp3/nghttp3.h> + +#include <ev.h> + +#include "client_base.h" +#include "tls_client_context.h" +#include "tls_client_session.h" +#include "network.h" +#include "shared.h" +#include "template.h" + +using namespace ngtcp2; + +struct Stream { + Stream(const Request &req, int64_t stream_id); + ~Stream(); + + int open_file(const std::string_view &path); + + Request req; + int64_t stream_id; + int fd; +}; + +class Client; + +struct Endpoint { + Address addr; + ev_io rev; + Client *client; + int fd; +}; + +class Client : public ClientBase { +public: + Client(struct ev_loop *loop, uint32_t client_chosen_version, + uint32_t original_version); + ~Client(); + + int init(int fd, const Address &local_addr, const Address &remote_addr, + const char *addr, const char *port, TLSClientContext &tls_ctx); + void disconnect(); + + int on_read(const Endpoint &ep); + int on_write(); + int write_streams(); + int feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen, + const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen); + int handle_expiry(); + void update_timer(); + int handshake_completed(); + int handshake_confirmed(); + void recv_version_negotiation(const uint32_t *sv, size_t nsv); + + int send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr, + unsigned int ecn, const uint8_t *data, size_t datalen); + int on_stream_close(int64_t stream_id, uint64_t app_error_code); + int on_extend_max_streams(); + int handle_error(); + int make_stream_early(); + int change_local_addr(); + void start_change_local_addr_timer(); + int update_key(uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen); + int initiate_key_update(); + void start_key_update_timer(); + void start_delay_stream_timer(); + + int select_preferred_address(Address &selected_addr, + const ngtcp2_preferred_addr *paddr); + + std::optional<Endpoint *> endpoint_for(const Address &remote_addr); + + void set_remote_addr(const ngtcp2_addr &remote_addr); + + int setup_httpconn(); + int submit_http_request(const Stream *stream); + int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data, + size_t datalen); + int acked_stream_data_offset(int64_t stream_id, uint64_t datalen); + void http_consume(int64_t stream_id, size_t nconsumed); + void http_write_data(int64_t stream_id, const uint8_t *data, size_t datalen); + int on_stream_reset(int64_t stream_id); + int on_stream_stop_sending(int64_t stream_id); + int extend_max_stream_data(int64_t stream_id, uint64_t max_data); + int stop_sending(int64_t stream_id, uint64_t app_error_code); + int reset_stream(int64_t stream_id, uint64_t app_error_code); + int http_stream_close(int64_t stream_id, uint64_t app_error_code); + + void on_send_blocked(const Endpoint &ep, const ngtcp2_addr &remote_addr, + unsigned int ecn, size_t datalen); + void start_wev_endpoint(const Endpoint &ep); + int send_blocked_packet(); + + const std::vector<uint32_t> &get_offered_versions() const; + + bool get_early_data() const; + void early_data_rejected(); + +private: + std::vector<Endpoint> endpoints_; + Address remote_addr_; + ev_io wev_; + ev_timer timer_; + ev_timer change_local_addr_timer_; + ev_timer key_update_timer_; + ev_timer delay_stream_timer_; + ev_signal sigintev_; + struct ev_loop *loop_; + std::map<int64_t, std::unique_ptr<Stream>> streams_; + std::vector<uint32_t> offered_versions_; + nghttp3_conn *httpconn_; + // addr_ is the server host address. + const char *addr_; + // port_ is the server port. + const char *port_; + // nstreams_done_ is the number of streams opened. + size_t nstreams_done_; + // nstreams_closed_ is the number of streams get closed. + size_t nstreams_closed_; + // nkey_update_ is the number of key update occurred. + size_t nkey_update_; + uint32_t client_chosen_version_; + uint32_t original_version_; + // early_data_ is true if client attempts to do 0RTT data transfer. + bool early_data_; + // should_exit_ is true if client should exit rather than waiting + // for timeout. + bool should_exit_; + // should_exit_on_handshake_confirmed_ is true if client should exit + // when handshake confirmed. + bool should_exit_on_handshake_confirmed_; + // handshake_confirmed_ gets true after handshake has been + // confirmed. + bool handshake_confirmed_; + + struct { + bool send_blocked; + // blocked field is effective only when send_blocked is true. + struct { + const Endpoint *endpoint; + Address remote_addr; + unsigned int ecn; + size_t datalen; + } blocked; + std::array<uint8_t, 64_k> data; + } tx_; +}; + +#endif // CLIENT_H diff --git a/examples/client_base.cc b/examples/client_base.cc new file mode 100644 index 0000000..aa35b4b --- /dev/null +++ b/examples/client_base.cc @@ -0,0 +1,202 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "client_base.h" + +#include <cassert> +#include <array> +#include <iostream> +#include <fstream> + +#include "debug.h" +#include "template.h" +#include "util.h" + +using namespace ngtcp2; +using namespace std::literals; + +extern Config config; + +static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { + auto c = static_cast<ClientBase *>(conn_ref->user_data); + return c->conn(); +} + +ClientBase::ClientBase() + : conn_ref_{get_conn, this}, qlog_(nullptr), conn_(nullptr) { + ngtcp2_connection_close_error_default(&last_error_); +} + +ClientBase::~ClientBase() { + if (conn_) { + ngtcp2_conn_del(conn_); + } + + if (qlog_) { + fclose(qlog_); + } +} + +int ClientBase::write_transport_params(const char *path, + const ngtcp2_transport_params *params) { + auto f = std::ofstream(path); + if (!f) { + return -1; + } + + f << "initial_max_streams_bidi=" << params->initial_max_streams_bidi << '\n' + << "initial_max_streams_uni=" << params->initial_max_streams_uni << '\n' + << "initial_max_stream_data_bidi_local=" + << params->initial_max_stream_data_bidi_local << '\n' + << "initial_max_stream_data_bidi_remote=" + << params->initial_max_stream_data_bidi_remote << '\n' + << "initial_max_stream_data_uni=" << params->initial_max_stream_data_uni + << '\n' + << "initial_max_data=" << params->initial_max_data << '\n' + << "active_connection_id_limit=" << params->active_connection_id_limit + << '\n' + << "max_datagram_frame_size=" << params->max_datagram_frame_size << '\n'; + + f.close(); + if (!f) { + return -1; + } + + return 0; +} + +int ClientBase::read_transport_params(const char *path, + ngtcp2_transport_params *params) { + auto f = std::ifstream(path); + if (!f) { + return -1; + } + + for (std::string line; std::getline(f, line);) { + if (util::istarts_with(line, "initial_max_streams_bidi="sv)) { + if (auto n = util::parse_uint(line.c_str() + + "initial_max_streams_bidi="sv.size()); + !n) { + return -1; + } else { + params->initial_max_streams_bidi = *n; + } + continue; + } + + if (util::istarts_with(line, "initial_max_streams_uni="sv)) { + if (auto n = util::parse_uint(line.c_str() + + "initial_max_streams_uni="sv.size()); + !n) { + return -1; + } else { + params->initial_max_streams_uni = *n; + } + continue; + } + + if (util::istarts_with(line, "initial_max_stream_data_bidi_local="sv)) { + if (auto n = util::parse_uint( + line.c_str() + "initial_max_stream_data_bidi_local="sv.size()); + !n) { + return -1; + } else { + params->initial_max_stream_data_bidi_local = *n; + } + continue; + } + + if (util::istarts_with(line, "initial_max_stream_data_bidi_remote="sv)) { + if (auto n = util::parse_uint( + line.c_str() + "initial_max_stream_data_bidi_remote="sv.size()); + !n) { + return -1; + } else { + params->initial_max_stream_data_bidi_remote = *n; + } + continue; + } + + if (util::istarts_with(line, "initial_max_stream_data_uni="sv)) { + if (auto n = util::parse_uint(line.c_str() + + "initial_max_stream_data_uni="sv.size()); + !n) { + return -1; + } else { + params->initial_max_stream_data_uni = *n; + } + continue; + } + + if (util::istarts_with(line, "initial_max_data="sv)) { + if (auto n = + util::parse_uint(line.c_str() + "initial_max_data="sv.size()); + !n) { + return -1; + } else { + params->initial_max_data = *n; + } + continue; + } + + if (util::istarts_with(line, "active_connection_id_limit="sv)) { + if (auto n = util::parse_uint(line.c_str() + + "active_connection_id_limit="sv.size()); + !n) { + return -1; + } else { + params->active_connection_id_limit = *n; + } + continue; + } + + if (util::istarts_with(line, "max_datagram_frame_size="sv)) { + if (auto n = util::parse_uint(line.c_str() + + "max_datagram_frame_size="sv.size()); + !n) { + return -1; + } else { + params->max_datagram_frame_size = *n; + } + continue; + } + } + + return 0; +} + +ngtcp2_conn *ClientBase::conn() const { return conn_; } + +void qlog_write_cb(void *user_data, uint32_t flags, const void *data, + size_t datalen) { + auto c = static_cast<ClientBase *>(user_data); + c->write_qlog(data, datalen); +} + +void ClientBase::write_qlog(const void *data, size_t datalen) { + assert(qlog_); + fwrite(data, 1, datalen, qlog_); +} + +ngtcp2_crypto_conn_ref *ClientBase::conn_ref() { return &conn_ref_; } diff --git a/examples/client_base.h b/examples/client_base.h new file mode 100644 index 0000000..ef364ea --- /dev/null +++ b/examples/client_base.h @@ -0,0 +1,212 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef CLIENT_BASE_H +#define CLIENT_BASE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <vector> +#include <deque> +#include <string> +#include <string_view> +#include <functional> + +#include <ngtcp2/ngtcp2_crypto.h> + +#include "tls_client_session.h" +#include "network.h" +#include "shared.h" + +using namespace ngtcp2; + +struct Request { + std::string_view scheme; + std::string authority; + std::string path; +}; + +struct Config { + ngtcp2_cid dcid; + ngtcp2_cid scid; + bool scid_present; + // tx_loss_prob is probability of losing outgoing packet. + double tx_loss_prob; + // rx_loss_prob is probability of losing incoming packet. + double rx_loss_prob; + // fd is a file descriptor to read input for streams. + int fd; + // ciphers is the list of enabled ciphers. + const char *ciphers; + // groups is the list of supported groups. + const char *groups; + // nstreams is the number of streams to open. + size_t nstreams; + // data is the pointer to memory region which maps file denoted by + // fd. + uint8_t *data; + // datalen is the length of file denoted by fd. + size_t datalen; + // version is a QUIC version to use. + uint32_t version; + // quiet suppresses the output normally shown except for the error + // messages. + bool quiet; + // timeout is an idle timeout for QUIC connection. + ngtcp2_duration timeout; + // session_file is a path to a file to write, and read TLS session. + const char *session_file; + // tp_file is a path to a file to write, and read QUIC transport + // parameters. + const char *tp_file; + // show_secret is true if transport secrets should be printed out. + bool show_secret; + // change_local_addr is the duration after which client changes + // local address. + ngtcp2_duration change_local_addr; + // key_update is the duration after which client initiates key + // update. + ngtcp2_duration key_update; + // delay_stream is the duration after which client sends the first + // 1-RTT stream. + ngtcp2_duration delay_stream; + // nat_rebinding is true if simulated NAT rebinding is enabled. + bool nat_rebinding; + // no_preferred_addr is true if client do not follow preferred + // address offered by server. + bool no_preferred_addr; + std::string_view http_method; + // download is a path to a directory where a downloaded file is + // saved. If it is empty, no file is saved. + std::string_view download; + // requests contains URIs to request. + std::vector<Request> requests; + // no_quic_dump is true if hexdump of QUIC STREAM and CRYPTO data + // should be disabled. + bool no_quic_dump; + // no_http_dump is true if hexdump of HTTP response body should be + // disabled. + bool no_http_dump; + // qlog_file is the path to write qlog. + std::string_view qlog_file; + // qlog_dir is the path to directory where qlog is stored. qlog_dir + // and qlog_file are mutually exclusive. + std::string_view qlog_dir; + // max_data is the initial connection-level flow control window. + uint64_t max_data; + // max_stream_data_bidi_local is the initial stream-level flow + // control window for a bidirectional stream that the local endpoint + // initiates. + uint64_t max_stream_data_bidi_local; + // max_stream_data_bidi_remote is the initial stream-level flow + // control window for a bidirectional stream that the remote + // endpoint initiates. + uint64_t max_stream_data_bidi_remote; + // max_stream_data_uni is the initial stream-level flow control + // window for a unidirectional stream. + uint64_t max_stream_data_uni; + // max_streams_bidi is the number of the concurrent bidirectional + // streams. + uint64_t max_streams_bidi; + // max_streams_uni is the number of the concurrent unidirectional + // streams. + uint64_t max_streams_uni; + // max_window is the maximum connection-level flow control window + // size if auto-tuning is enabled. + uint64_t max_window; + // max_stream_window is the maximum stream-level flow control window + // size if auto-tuning is enabled. + uint64_t max_stream_window; + // exit_on_first_stream_close is the flag that if it is true, client + // exits when a first HTTP stream gets closed. It is not + // necessarily the same time when the underlying QUIC stream closes + // due to the QPACK synchronization. + bool exit_on_first_stream_close; + // exit_on_all_streams_close is the flag that if it is true, client + // exits when all HTTP streams get closed. + bool exit_on_all_streams_close; + // disable_early_data disables early data. + bool disable_early_data; + // static_secret is used to derive keying materials for Stateless + // Retry token. + std::array<uint8_t, 32> static_secret; + // cc_algo is the congestion controller algorithm. + ngtcp2_cc_algo cc_algo; + // token_file is a path to file to read or write token from + // NEW_TOKEN frame. + std::string_view token_file; + // sni is the value sent in TLS SNI, overriding DNS name of the + // remote host. + std::string_view sni; + // initial_rtt is an initial RTT. + ngtcp2_duration initial_rtt; + // max_udp_payload_size is the maximum UDP payload size that client + // transmits. + size_t max_udp_payload_size; + // handshake_timeout is the period of time before giving up QUIC + // connection establishment. + ngtcp2_duration handshake_timeout; + // preferred_versions includes QUIC versions in the order of + // preference. Client uses this field to select a version from the + // version set offered in Version Negotiation packet. + std::vector<uint32_t> preferred_versions; + // other_versions includes QUIC versions that are sent in + // other_versions field of version_information transport_parameter. + std::vector<uint32_t> other_versions; + // no_pmtud disables Path MTU Discovery. + bool no_pmtud; + // ack_thresh is the minimum number of the received ACK eliciting + // packets that triggers immediate acknowledgement. + size_t ack_thresh; +}; + +class ClientBase { +public: + ClientBase(); + ~ClientBase(); + + ngtcp2_conn *conn() const; + + int write_transport_params(const char *path, + const ngtcp2_transport_params *params); + int read_transport_params(const char *path, ngtcp2_transport_params *params); + + void write_qlog(const void *data, size_t datalen); + + ngtcp2_crypto_conn_ref *conn_ref(); + +protected: + ngtcp2_crypto_conn_ref conn_ref_; + TLSClientSession tls_session_; + FILE *qlog_; + ngtcp2_conn *conn_; + ngtcp2_connection_close_error last_error_; +}; + +void qlog_write_cb(void *user_data, uint32_t flags, const void *data, + size_t datalen); + +#endif // CLIENT_BASE_H diff --git a/examples/debug.cc b/examples/debug.cc new file mode 100644 index 0000000..98561fb --- /dev/null +++ b/examples/debug.cc @@ -0,0 +1,298 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "debug.h" + +#include <cassert> +#include <random> +#include <iostream> + +#include "util.h" + +using namespace std::literals; + +namespace ngtcp2 { + +namespace debug { + +namespace { +auto randgen = util::make_mt19937(); +} // namespace + +namespace { +auto *outfile = stderr; +} // namespace + +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + fprintf(outfile, "QUIC handshake has completed\n"); + return 0; +} + +int handshake_confirmed(ngtcp2_conn *conn, void *user_data) { + fprintf(outfile, "QUIC handshake has been confirmed\n"); + return 0; +} + +bool packet_lost(double prob) { + auto p = std::uniform_real_distribution<>(0, 1)(randgen); + return p < prob; +} + +void print_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data, + size_t datalen) { + const char *crypto_level_str; + switch (crypto_level) { + case NGTCP2_CRYPTO_LEVEL_INITIAL: + crypto_level_str = "Initial"; + break; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + crypto_level_str = "Handshake"; + break; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + crypto_level_str = "Application"; + break; + default: + assert(0); + abort(); + } + fprintf(outfile, "Ordered CRYPTO data in %s crypto level\n", + crypto_level_str); + util::hexdump(outfile, data, datalen); +} + +void print_stream_data(int64_t stream_id, const uint8_t *data, size_t datalen) { + fprintf(outfile, "Ordered STREAM data stream_id=0x%" PRIx64 "\n", stream_id); + util::hexdump(outfile, data, datalen); +} + +void print_initial_secret(const uint8_t *data, size_t len) { + fprintf(outfile, "initial_secret=%s\n", util::format_hex(data, len).c_str()); +} + +void print_client_in_secret(const uint8_t *data, size_t len) { + fprintf(outfile, "client_in_secret=%s\n", + util::format_hex(data, len).c_str()); +} + +void print_server_in_secret(const uint8_t *data, size_t len) { + fprintf(outfile, "server_in_secret=%s\n", + util::format_hex(data, len).c_str()); +} + +void print_handshake_secret(const uint8_t *data, size_t len) { + fprintf(outfile, "handshake_secret=%s\n", + util::format_hex(data, len).c_str()); +} + +void print_client_hs_secret(const uint8_t *data, size_t len) { + fprintf(outfile, "client_hs_secret=%s\n", + util::format_hex(data, len).c_str()); +} + +void print_server_hs_secret(const uint8_t *data, size_t len) { + fprintf(outfile, "server_hs_secret=%s\n", + util::format_hex(data, len).c_str()); +} + +void print_client_0rtt_secret(const uint8_t *data, size_t len) { + fprintf(outfile, "client_0rtt_secret=%s\n", + util::format_hex(data, len).c_str()); +} + +void print_client_1rtt_secret(const uint8_t *data, size_t len) { + fprintf(outfile, "client_1rtt_secret=%s\n", + util::format_hex(data, len).c_str()); +} + +void print_server_1rtt_secret(const uint8_t *data, size_t len) { + fprintf(outfile, "server_1rtt_secret=%s\n", + util::format_hex(data, len).c_str()); +} + +void print_client_pp_key(const uint8_t *data, size_t len) { + fprintf(outfile, "+ client_pp_key=%s\n", util::format_hex(data, len).c_str()); +} + +void print_server_pp_key(const uint8_t *data, size_t len) { + fprintf(outfile, "+ server_pp_key=%s\n", util::format_hex(data, len).c_str()); +} + +void print_client_pp_iv(const uint8_t *data, size_t len) { + fprintf(outfile, "+ client_pp_iv=%s\n", util::format_hex(data, len).c_str()); +} + +void print_server_pp_iv(const uint8_t *data, size_t len) { + fprintf(outfile, "+ server_pp_iv=%s\n", util::format_hex(data, len).c_str()); +} + +void print_client_pp_hp(const uint8_t *data, size_t len) { + fprintf(outfile, "+ client_pp_hp=%s\n", util::format_hex(data, len).c_str()); +} + +void print_server_pp_hp(const uint8_t *data, size_t len) { + fprintf(outfile, "+ server_pp_hp=%s\n", util::format_hex(data, len).c_str()); +} + +void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key, + size_t keylen, const uint8_t *iv, size_t ivlen, + const uint8_t *hp, size_t hplen) { + std::cerr << "+ secret=" << util::format_hex(secret, secretlen) << "\n" + << "+ key=" << util::format_hex(key, keylen) << "\n" + << "+ iv=" << util::format_hex(iv, ivlen) << "\n" + << "+ hp=" << util::format_hex(hp, hplen) << std::endl; +} + +void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key, + size_t keylen, const uint8_t *iv, size_t ivlen) { + std::cerr << "+ secret=" << util::format_hex(secret, secretlen) << "\n" + << "+ key=" << util::format_hex(key, keylen) << "\n" + << "+ iv=" << util::format_hex(iv, ivlen) << std::endl; +} + +void print_hp_mask(const uint8_t *mask, size_t masklen, const uint8_t *sample, + size_t samplelen) { + fprintf(outfile, "mask=%s sample=%s\n", + util::format_hex(mask, masklen).c_str(), + util::format_hex(sample, samplelen).c_str()); +} + +void log_printf(void *user_data, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fprintf(stderr, "\n"); +} + +void path_validation(const ngtcp2_path *path, + ngtcp2_path_validation_result res) { + auto local_addr = util::straddr( + reinterpret_cast<sockaddr *>(path->local.addr), path->local.addrlen); + auto remote_addr = util::straddr( + reinterpret_cast<sockaddr *>(path->remote.addr), path->remote.addrlen); + + std::cerr << "Path validation against path {local:" << local_addr + << ", remote:" << remote_addr << "} " + << (res == NGTCP2_PATH_VALIDATION_RESULT_SUCCESS ? "succeeded" + : "failed") + << std::endl; +} + +void print_http_begin_request_headers(int64_t stream_id) { + fprintf(outfile, "http: stream 0x%" PRIx64 " request headers started\n", + stream_id); +} + +void print_http_begin_response_headers(int64_t stream_id) { + fprintf(outfile, "http: stream 0x%" PRIx64 " response headers started\n", + stream_id); +} + +namespace { +void print_header(const uint8_t *name, size_t namelen, const uint8_t *value, + size_t valuelen, uint8_t flags) { + fprintf(outfile, "[%.*s: %.*s]%s\n", static_cast<int>(namelen), name, + static_cast<int>(valuelen), value, + (flags & NGHTTP3_NV_FLAG_NEVER_INDEX) ? "(sensitive)" : ""); +} +} // namespace + +namespace { +void print_header(const nghttp3_rcbuf *name, const nghttp3_rcbuf *value, + uint8_t flags) { + auto namebuf = nghttp3_rcbuf_get_buf(name); + auto valuebuf = nghttp3_rcbuf_get_buf(value); + print_header(namebuf.base, namebuf.len, valuebuf.base, valuebuf.len, flags); +} +} // namespace + +namespace { +void print_header(const nghttp3_nv &nv) { + print_header(nv.name, nv.namelen, nv.value, nv.valuelen, nv.flags); +} +} // namespace + +void print_http_header(int64_t stream_id, const nghttp3_rcbuf *name, + const nghttp3_rcbuf *value, uint8_t flags) { + fprintf(outfile, "http: stream 0x%" PRIx64 " ", stream_id); + print_header(name, value, flags); +} + +void print_http_end_headers(int64_t stream_id) { + fprintf(outfile, "http: stream 0x%" PRIx64 " headers ended\n", stream_id); +} + +void print_http_data(int64_t stream_id, const uint8_t *data, size_t datalen) { + fprintf(outfile, "http: stream 0x%" PRIx64 " body %zu bytes\n", stream_id, + datalen); + util::hexdump(outfile, data, datalen); +} + +void print_http_begin_trailers(int64_t stream_id) { + fprintf(outfile, "http: stream 0x%" PRIx64 " trailers started\n", stream_id); +} + +void print_http_end_trailers(int64_t stream_id) { + fprintf(outfile, "http: stream 0x%" PRIx64 " trailers ended\n", stream_id); +} + +void print_http_request_headers(int64_t stream_id, const nghttp3_nv *nva, + size_t nvlen) { + fprintf(outfile, "http: stream 0x%" PRIx64 " submit request headers\n", + stream_id); + for (size_t i = 0; i < nvlen; ++i) { + auto &nv = nva[i]; + print_header(nv); + } +} + +void print_http_response_headers(int64_t stream_id, const nghttp3_nv *nva, + size_t nvlen) { + fprintf(outfile, "http: stream 0x%" PRIx64 " submit response headers\n", + stream_id); + for (size_t i = 0; i < nvlen; ++i) { + auto &nv = nva[i]; + print_header(nv); + } +} + +std::string_view secret_title(ngtcp2_crypto_level level) { + switch (level) { + case NGTCP2_CRYPTO_LEVEL_EARLY: + return "early_traffic"sv; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + return "handshake_traffic"sv; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + return "application_traffic"sv; + default: + assert(0); + abort(); + } +} + +} // namespace debug + +} // namespace ngtcp2 diff --git a/examples/debug.h b/examples/debug.h new file mode 100644 index 0000000..5b31388 --- /dev/null +++ b/examples/debug.h @@ -0,0 +1,124 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef DEBUG_H +#define DEBUG_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#ifndef __STDC_FORMAT_MACROS +// For travis and PRIu64 +# define __STDC_FORMAT_MACROS +#endif // __STDC_FORMAT_MACROS + +#include <cinttypes> +#include <string_view> + +#include <ngtcp2/ngtcp2.h> +#include <nghttp3/nghttp3.h> + +namespace ngtcp2 { + +namespace debug { + +int handshake_completed(ngtcp2_conn *conn, void *user_data); + +int handshake_confirmed(ngtcp2_conn *conn, void *user_data); + +bool packet_lost(double prob); + +void print_crypto_data(ngtcp2_crypto_level crypto_level, const uint8_t *data, + size_t datalen); + +void print_stream_data(int64_t stream_id, const uint8_t *data, size_t datalen); + +void print_initial_secret(const uint8_t *data, size_t len); + +void print_client_in_secret(const uint8_t *data, size_t len); +void print_server_in_secret(const uint8_t *data, size_t len); + +void print_handshake_secret(const uint8_t *data, size_t len); + +void print_client_hs_secret(const uint8_t *data, size_t len); +void print_server_hs_secret(const uint8_t *data, size_t len); + +void print_client_0rtt_secret(const uint8_t *data, size_t len); + +void print_client_1rtt_secret(const uint8_t *data, size_t len); +void print_server_1rtt_secret(const uint8_t *data, size_t len); + +void print_client_pp_key(const uint8_t *data, size_t len); +void print_server_pp_key(const uint8_t *data, size_t len); + +void print_client_pp_iv(const uint8_t *data, size_t len); +void print_server_pp_iv(const uint8_t *data, size_t len); + +void print_client_pp_hp(const uint8_t *data, size_t len); +void print_server_pp_hp(const uint8_t *data, size_t len); + +void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key, + size_t keylen, const uint8_t *iv, size_t ivlen, + const uint8_t *hp, size_t hplen); + +void print_secrets(const uint8_t *secret, size_t secretlen, const uint8_t *key, + size_t keylen, const uint8_t *iv, size_t ivlen); + +void print_hp_mask(const uint8_t *mask, size_t masklen, const uint8_t *sample, + size_t samplelen); + +void log_printf(void *user_data, const char *fmt, ...); + +void path_validation(const ngtcp2_path *path, + ngtcp2_path_validation_result res); + +void print_http_begin_request_headers(int64_t stream_id); + +void print_http_begin_response_headers(int64_t stream_id); + +void print_http_header(int64_t stream_id, const nghttp3_rcbuf *name, + const nghttp3_rcbuf *value, uint8_t flags); + +void print_http_end_headers(int64_t stream_id); + +void print_http_data(int64_t stream_id, const uint8_t *data, size_t datalen); + +void print_http_begin_trailers(int64_t stream_id); + +void print_http_end_trailers(int64_t stream_id); + +void print_http_request_headers(int64_t stream_id, const nghttp3_nv *nva, + size_t nvlen); + +void print_http_response_headers(int64_t stream_id, const nghttp3_nv *nva, + size_t nvlen); + +std::string_view secret_title(ngtcp2_crypto_level level); + +} // namespace debug + +} // namespace ngtcp2 + +#endif // DEBUG_H diff --git a/examples/examplestest.cc b/examples/examplestest.cc new file mode 100644 index 0000000..6487f81 --- /dev/null +++ b/examples/examplestest.cc @@ -0,0 +1,84 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * Copyright (c) 2013 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <stdio.h> +#include <CUnit/Basic.h> +// include test cases' include files here +#include "util_test.h" + +static int init_suite1(void) { return 0; } + +static int clean_suite1(void) { return 0; } + +int main(int argc, char *argv[]) { + CU_pSuite pSuite = nullptr; + unsigned int num_tests_failed; + + // initialize the CUnit test registry + if (CUE_SUCCESS != CU_initialize_registry()) + return CU_get_error(); + + // add a suite to the registry + pSuite = CU_add_suite("TestSuite", init_suite1, clean_suite1); + if (nullptr == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + + // add the tests to the suite + if (!CU_add_test(pSuite, "util_format_durationf", + ngtcp2::test_util_format_durationf) || + !CU_add_test(pSuite, "util_format_uint", ngtcp2::test_util_format_uint) || + !CU_add_test(pSuite, "util_format_uint_iec", + ngtcp2::test_util_format_uint_iec) || + !CU_add_test(pSuite, "util_format_duration", + ngtcp2::test_util_format_duration) || + !CU_add_test(pSuite, "util_parse_uint", ngtcp2::test_util_parse_uint) || + !CU_add_test(pSuite, "util_parse_uint_iec", + ngtcp2::test_util_parse_uint_iec) || + !CU_add_test(pSuite, "util_parse_duration", + ngtcp2::test_util_parse_duration) || + !CU_add_test(pSuite, "util_normalize_path", + ngtcp2::test_util_normalize_path)) { + CU_cleanup_registry(); + return CU_get_error(); + } + + // Run all tests using the CUnit Basic interface + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_tests_failed = CU_get_number_of_tests_failed(); + CU_cleanup_registry(); + if (CU_get_error() == CUE_SUCCESS) { + return num_tests_failed; + } else { + printf("CUnit Error: %s\n", CU_get_error_msg()); + return CU_get_error(); + } +} diff --git a/examples/gtlssimpleclient.c b/examples/gtlssimpleclient.c new file mode 100644 index 0000000..60c0121 --- /dev/null +++ b/examples/gtlssimpleclient.c @@ -0,0 +1,720 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021-2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#include <ngtcp2/ngtcp2.h> +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_gnutls.h> + +#include <gnutls/crypto.h> +#include <gnutls/gnutls.h> + +#include <ev.h> + +#define REMOTE_HOST "127.0.0.1" +#define REMOTE_PORT "4433" +#define ALPN "hq-interop" +#define MESSAGE "GET /\r\n" + +/* + * Example 1: Handshake with www.google.com + * + * #define REMOTE_HOST "www.google.com" + * #define REMOTE_PORT "443" + * #define ALPN "h3" + * + * and undefine MESSAGE macro. + */ + +static uint64_t timestamp(void) { + struct timespec tp; + + if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { + fprintf(stderr, "clock_gettime: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + return (uint64_t)tp.tv_sec * NGTCP2_SECONDS + (uint64_t)tp.tv_nsec; +} + +static int create_sock(struct sockaddr *addr, socklen_t *paddrlen, + const char *host, const char *port) { + struct addrinfo hints = {0}; + struct addrinfo *res, *rp; + int rv; + int fd = -1; + + hints.ai_flags = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + rv = getaddrinfo(host, port, &hints, &res); + if (rv != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return -1; + } + + for (rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + continue; + } + + break; + } + + if (fd == -1) { + goto end; + } + + *paddrlen = rp->ai_addrlen; + memcpy(addr, rp->ai_addr, rp->ai_addrlen); + +end: + freeaddrinfo(res); + + return fd; +} + +static int connect_sock(struct sockaddr *local_addr, socklen_t *plocal_addrlen, + int fd, const struct sockaddr *remote_addr, + size_t remote_addrlen) { + socklen_t len; + + if (connect(fd, remote_addr, (socklen_t)remote_addrlen) != 0) { + fprintf(stderr, "connect: %s\n", strerror(errno)); + return -1; + } + + len = *plocal_addrlen; + + if (getsockname(fd, local_addr, &len) == -1) { + fprintf(stderr, "getsockname: %s\n", strerror(errno)); + return -1; + } + + *plocal_addrlen = len; + + return 0; +} + +struct client { + ngtcp2_crypto_conn_ref conn_ref; + int fd; + struct sockaddr_storage local_addr; + socklen_t local_addrlen; + gnutls_certificate_credentials_t cred; + gnutls_session_t session; + ngtcp2_conn *conn; + + struct { + int64_t stream_id; + const uint8_t *data; + size_t datalen; + size_t nwrite; + } stream; + + ngtcp2_connection_close_error last_error; + + ev_io rev; + ev_timer timer; +}; + +static int hook_func(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) { + (void)session; + (void)htype; + (void)when; + (void)incoming; + (void)msg; + /* we could save session data here */ + + return 0; +} + +static int numeric_host_family(const char *hostname, int family) { + uint8_t dst[sizeof(struct in6_addr)]; + return inet_pton(family, hostname, dst) == 1; +} + +static int numeric_host(const char *hostname) { + return numeric_host_family(hostname, AF_INET) || + numeric_host_family(hostname, AF_INET6); +} + +static const char priority[] = + "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" + "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:" + "+GROUP-SECP384R1:" + "+GROUP-SECP521R1:%DISABLE_TLS13_COMPAT_MODE"; + +static const gnutls_datum_t alpn = {(uint8_t *)ALPN, sizeof(ALPN) - 1}; + +static int client_gnutls_init(struct client *c) { + int rv = gnutls_certificate_allocate_credentials(&c->cred); + + if (rv == 0) + rv = gnutls_certificate_set_x509_system_trust(c->cred); + if (rv < 0) { + fprintf(stderr, "cred init failed: %d: %s\n", rv, gnutls_strerror(rv)); + return -1; + } + + rv = gnutls_init(&c->session, GNUTLS_CLIENT | GNUTLS_ENABLE_EARLY_DATA | + GNUTLS_NO_END_OF_EARLY_DATA); + if (rv != 0) { + fprintf(stderr, "gnutls_init: %s\n", gnutls_strerror(rv)); + return -1; + } + + if (ngtcp2_crypto_gnutls_configure_client_session(c->session) != 0) { + fprintf(stderr, "ngtcp2_crypto_gnutls_configure_client_session failed\n"); + return -1; + } + + rv = gnutls_priority_set_direct(c->session, priority, NULL); + if (rv != 0) { + fprintf(stderr, "gnutls_priority_set_direct: %s\n", gnutls_strerror(rv)); + return -1; + } + + gnutls_handshake_set_hook_function(c->session, GNUTLS_HANDSHAKE_ANY, + GNUTLS_HOOK_POST, hook_func); + + gnutls_session_set_ptr(c->session, &c->conn_ref); + + rv = gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred); + + if (rv != 0) { + fprintf(stderr, "gnutls_credentials_set: %s\n", gnutls_strerror(rv)); + return -1; + } + + gnutls_alpn_set_protocols(c->session, &alpn, 1, GNUTLS_ALPN_MANDATORY); + + if (!numeric_host(REMOTE_HOST)) { + gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, REMOTE_HOST, + strlen(REMOTE_HOST)); + } else { + gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, "localhost", + strlen("localhost")); + } + + return 0; +} + +static void rand_cb(uint8_t *dest, size_t destlen, + const ngtcp2_rand_ctx *rand_ctx) { + (void)rand_ctx; + + (void)gnutls_rnd(GNUTLS_RND_RANDOM, dest, destlen); +} + +static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data) { + (void)conn; + (void)user_data; + + if (gnutls_rnd(GNUTLS_RND_RANDOM, cid->data, cidlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + cid->datalen = cidlen; + + if (gnutls_rnd(GNUTLS_RND_RANDOM, token, NGTCP2_STATELESS_RESET_TOKENLEN) != + 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int extend_max_local_streams_bidi(ngtcp2_conn *conn, + uint64_t max_streams, + void *user_data) { +#ifdef MESSAGE + struct client *c = user_data; + int rv; + int64_t stream_id; + (void)max_streams; + + if (c->stream.stream_id != -1) { + return 0; + } + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + if (rv != 0) { + return 0; + } + + c->stream.stream_id = stream_id; + c->stream.data = (const uint8_t *)MESSAGE; + c->stream.datalen = sizeof(MESSAGE) - 1; + + return 0; +#else /* !MESSAGE */ + (void)conn; + (void)max_streams; + (void)user_data; + + return 0; +#endif /* !MESSAGE */ +} + +static void log_printf(void *user_data, const char *fmt, ...) { + va_list ap; + (void)user_data; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fprintf(stderr, "\n"); +} + +static int client_quic_init(struct client *c, + const struct sockaddr *remote_addr, + socklen_t remote_addrlen, + const struct sockaddr *local_addr, + socklen_t local_addrlen) { + ngtcp2_path path = { + { + (struct sockaddr *)local_addr, + local_addrlen, + }, + { + (struct sockaddr *)remote_addr, + remote_addrlen, + }, + NULL, + }; + ngtcp2_callbacks callbacks = { + ngtcp2_crypto_client_initial_cb, + NULL, /* recv_client_initial */ + ngtcp2_crypto_recv_crypto_data_cb, + NULL, /* handshake_completed */ + NULL, /* recv_version_negotiation */ + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + NULL, /* recv_stream_data */ + NULL, /* acked_stream_data_offset */ + NULL, /* stream_open */ + NULL, /* stream_close */ + NULL, /* recv_stateless_reset */ + ngtcp2_crypto_recv_retry_cb, + extend_max_local_streams_bidi, + NULL, /* extend_max_local_streams_uni */ + rand_cb, + get_new_connection_id_cb, + NULL, /* remove_connection_id */ + ngtcp2_crypto_update_key_cb, + NULL, /* path_validation */ + NULL, /* select_preferred_address */ + NULL, /* stream_reset */ + NULL, /* extend_max_remote_streams_bidi */ + NULL, /* extend_max_remote_streams_uni */ + NULL, /* extend_max_stream_data */ + NULL, /* dcid_status */ + NULL, /* handshake_confirmed */ + NULL, /* recv_new_token */ + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + NULL, /* recv_datagram */ + NULL, /* ack_datagram */ + NULL, /* lost_datagram */ + ngtcp2_crypto_get_path_challenge_data_cb, + NULL, /* stream_stop_sending */ + ngtcp2_crypto_version_negotiation_cb, + NULL, /* recv_rx_key */ + NULL, /* recv_tx_key */ + NULL, /* early_data_rejected */ + }; + ngtcp2_cid dcid, scid; + ngtcp2_settings settings; + ngtcp2_transport_params params; + int rv; + + dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN; + if (gnutls_rnd(GNUTLS_RND_RANDOM, dcid.data, dcid.datalen) != 0) { + fprintf(stderr, "gnutls_rnd failed\n"); + return -1; + } + + scid.datalen = 8; + if (gnutls_rnd(GNUTLS_RND_RANDOM, scid.data, scid.datalen) != 0) { + fprintf(stderr, "gnutls_rnd failed\n"); + return -1; + } + + ngtcp2_settings_default(&settings); + + settings.initial_ts = timestamp(); + settings.log_printf = log_printf; + + ngtcp2_transport_params_default(¶ms); + + params.initial_max_streams_uni = 3; + params.initial_max_stream_data_bidi_local = 128 * 1024; + params.initial_max_data = 1024 * 1024; + + rv = + ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, + &callbacks, &settings, ¶ms, NULL, c); + if (rv != 0) { + fprintf(stderr, "ngtcp2_conn_client_new: %s\n", ngtcp2_strerror(rv)); + return -1; + } + + ngtcp2_conn_set_tls_native_handle(c->conn, c->session); + + return 0; +} + +static int client_read(struct client *c) { + uint8_t buf[65536]; + struct sockaddr_storage addr; + struct iovec iov = {buf, sizeof(buf)}; + struct msghdr msg = {0}; + ssize_t nread; + ngtcp2_path path; + ngtcp2_pkt_info pi = {0}; + int rv; + + msg.msg_name = &addr; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + for (;;) { + msg.msg_namelen = sizeof(addr); + + nread = recvmsg(c->fd, &msg, MSG_DONTWAIT); + + if (nread == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + fprintf(stderr, "recvmsg: %s\n", strerror(errno)); + } + + break; + } + + path.local.addrlen = c->local_addrlen; + path.local.addr = (struct sockaddr *)&c->local_addr; + path.remote.addrlen = msg.msg_namelen; + path.remote.addr = msg.msg_name; + + rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (size_t)nread, + timestamp()); + if (rv != 0) { + fprintf(stderr, "ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv)); + if (!c->last_error.error_code) { + if (rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_connection_close_error_set_transport_error_tls_alert( + &c->last_error, ngtcp2_conn_get_tls_alert(c->conn), NULL, 0); + } else { + ngtcp2_connection_close_error_set_transport_error_liberr( + &c->last_error, rv, NULL, 0); + } + } + return -1; + } + } + + return 0; +} + +static int client_send_packet(struct client *c, const uint8_t *data, + size_t datalen) { + struct iovec iov = {(uint8_t *)data, datalen}; + struct msghdr msg = {0}; + ssize_t nwrite; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + do { + nwrite = sendmsg(c->fd, &msg, 0); + } while (nwrite == -1 && errno == EINTR); + + if (nwrite == -1) { + fprintf(stderr, "sendmsg: %s\n", strerror(errno)); + + return -1; + } + + return 0; +} + +static size_t client_get_message(struct client *c, int64_t *pstream_id, + int *pfin, ngtcp2_vec *datav, + size_t datavcnt) { + if (datavcnt == 0) { + return 0; + } + + if (c->stream.stream_id != -1 && c->stream.nwrite < c->stream.datalen) { + *pstream_id = c->stream.stream_id; + *pfin = 1; + datav->base = (uint8_t *)c->stream.data + c->stream.nwrite; + datav->len = c->stream.datalen - c->stream.nwrite; + return 1; + } + + *pstream_id = -1; + *pfin = 0; + datav->base = NULL; + datav->len = 0; + + return 0; +} + +static int client_write_streams(struct client *c) { + ngtcp2_tstamp ts = timestamp(); + ngtcp2_pkt_info pi; + ngtcp2_ssize nwrite; + uint8_t buf[1280]; + ngtcp2_path_storage ps; + ngtcp2_vec datav; + size_t datavcnt; + int64_t stream_id; + ngtcp2_ssize wdatalen; + uint32_t flags; + int fin; + + ngtcp2_path_storage_zero(&ps); + + for (;;) { + datavcnt = client_get_message(c, &stream_id, &fin, &datav, 1); + + flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + if (fin) { + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + + nwrite = ngtcp2_conn_writev_stream(c->conn, &ps.path, &pi, buf, sizeof(buf), + &wdatalen, flags, stream_id, &datav, + datavcnt, ts); + if (nwrite < 0) { + switch (nwrite) { + case NGTCP2_ERR_WRITE_MORE: + c->stream.nwrite += (size_t)wdatalen; + continue; + default: + fprintf(stderr, "ngtcp2_conn_writev_stream: %s\n", + ngtcp2_strerror((int)nwrite)); + ngtcp2_connection_close_error_set_transport_error_liberr( + &c->last_error, (int)nwrite, NULL, 0); + return -1; + } + } + + if (nwrite == 0) { + return 0; + } + + if (wdatalen > 0) { + c->stream.nwrite += (size_t)wdatalen; + } + + if (client_send_packet(c, buf, (size_t)nwrite) != 0) { + break; + } + } + + return 0; +} + +static int client_write(struct client *c) { + ngtcp2_tstamp expiry, now; + ev_tstamp t; + + if (client_write_streams(c) != 0) { + return -1; + } + + expiry = ngtcp2_conn_get_expiry(c->conn); + now = timestamp(); + + t = expiry < now ? 1e-9 : (ev_tstamp)(expiry - now) / NGTCP2_SECONDS; + + c->timer.repeat = t; + ev_timer_again(EV_DEFAULT, &c->timer); + + return 0; +} + +static int client_handle_expiry(struct client *c) { + int rv = ngtcp2_conn_handle_expiry(c->conn, timestamp()); + if (rv != 0) { + fprintf(stderr, "ngtcp2_conn_handle_expiry: %s\n", ngtcp2_strerror(rv)); + return -1; + } + + return 0; +} + +static void client_close(struct client *c) { + ngtcp2_ssize nwrite; + ngtcp2_pkt_info pi; + ngtcp2_path_storage ps; + uint8_t buf[1280]; + + if (ngtcp2_conn_is_in_closing_period(c->conn) || + ngtcp2_conn_is_in_draining_period(c->conn)) { + goto fin; + } + + ngtcp2_path_storage_zero(&ps); + + nwrite = ngtcp2_conn_write_connection_close( + c->conn, &ps.path, &pi, buf, sizeof(buf), &c->last_error, timestamp()); + if (nwrite < 0) { + fprintf(stderr, "ngtcp2_conn_write_connection_close: %s\n", + ngtcp2_strerror((int)nwrite)); + goto fin; + } + + client_send_packet(c, buf, (size_t)nwrite); + +fin: + ev_break(EV_DEFAULT, EVBREAK_ALL); +} + +static void read_cb(struct ev_loop *loop, ev_io *w, int revents) { + struct client *c = w->data; + (void)loop; + (void)revents; + + if (client_read(c) != 0) { + client_close(c); + return; + } + + if (client_write(c) != 0) { + client_close(c); + } +} + +static void timer_cb(struct ev_loop *loop, ev_timer *w, int revents) { + struct client *c = w->data; + (void)loop; + (void)revents; + + if (client_handle_expiry(c) != 0) { + client_close(c); + return; + } + + if (client_write(c) != 0) { + client_close(c); + } +} + +static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { + struct client *c = conn_ref->user_data; + return c->conn; +} + +static int client_init(struct client *c) { + struct sockaddr_storage remote_addr, local_addr; + socklen_t remote_addrlen, local_addrlen = sizeof(local_addr); + + memset(c, 0, sizeof(*c)); + + ngtcp2_connection_close_error_default(&c->last_error); + + c->fd = create_sock((struct sockaddr *)&remote_addr, &remote_addrlen, + REMOTE_HOST, REMOTE_PORT); + if (c->fd == -1) { + return -1; + } + + if (connect_sock((struct sockaddr *)&local_addr, &local_addrlen, c->fd, + (struct sockaddr *)&remote_addr, remote_addrlen) != 0) { + return -1; + } + + memcpy(&c->local_addr, &local_addr, sizeof(c->local_addr)); + c->local_addrlen = local_addrlen; + + if (client_gnutls_init(c) != 0) { + return -1; + } + + if (client_quic_init(c, (struct sockaddr *)&remote_addr, remote_addrlen, + (struct sockaddr *)&local_addr, local_addrlen) != 0) { + return -1; + } + + c->stream.stream_id = -1; + + c->conn_ref.get_conn = get_conn; + c->conn_ref.user_data = c; + + ev_io_init(&c->rev, read_cb, c->fd, EV_READ); + c->rev.data = c; + ev_io_start(EV_DEFAULT, &c->rev); + + ev_timer_init(&c->timer, timer_cb, 0., 0.); + c->timer.data = c; + + return 0; +} + +static void client_free(struct client *c) { + ngtcp2_conn_del(c->conn); + gnutls_deinit(c->session); + gnutls_certificate_free_credentials(c->cred); +} + +int main(void) { + struct client c; + + if (client_init(&c) != 0) { + exit(EXIT_FAILURE); + } + + if (client_write(&c) != 0) { + exit(EXIT_FAILURE); + } + + ev_run(EV_DEFAULT, 0); + + client_free(&c); + + return 0; +} diff --git a/examples/h09client.cc b/examples/h09client.cc new file mode 100644 index 0000000..4df12ca --- /dev/null +++ b/examples/h09client.cc @@ -0,0 +1,2604 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include <cstdlib> +#include <cassert> +#include <cerrno> +#include <iostream> +#include <algorithm> +#include <memory> +#include <fstream> +#include <iomanip> + +#include <unistd.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <netdb.h> +#include <sys/mman.h> + +#include <http-parser/http_parser.h> + +#include "h09client.h" +#include "network.h" +#include "debug.h" +#include "util.h" +#include "shared.h" + +using namespace ngtcp2; +using namespace std::literals; + +namespace { +auto randgen = util::make_mt19937(); +} // namespace + +namespace { +constexpr size_t max_preferred_versionslen = 4; +} // namespace + +Config config{}; + +Stream::Stream(const Request &req, int64_t stream_id) + : req(req), stream_id(stream_id), fd(-1) { + nghttp3_buf_init(&reqbuf); +} + +Stream::~Stream() { + if (fd != -1) { + close(fd); + } +} + +int Stream::open_file(const std::string_view &path) { + assert(fd == -1); + + std::string_view filename; + + auto it = std::find(std::rbegin(path), std::rend(path), '/').base(); + if (it == std::end(path)) { + filename = "index.html"sv; + } else { + filename = std::string_view{it, static_cast<size_t>(std::end(path) - it)}; + if (filename == ".."sv || filename == "."sv) { + std::cerr << "Invalid file name: " << filename << std::endl; + return -1; + } + } + + auto fname = std::string{config.download}; + fname += '/'; + fname += filename; + + fd = open(fname.c_str(), O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd == -1) { + std::cerr << "open: Could not open file " << fname << ": " + << strerror(errno) << std::endl; + return -1; + } + + return 0; +} + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto c = static_cast<Client *>(w->data); + + c->on_write(); +} +} // namespace + +namespace { +void readcb(struct ev_loop *loop, ev_io *w, int revents) { + auto ep = static_cast<Endpoint *>(w->data); + auto c = ep->client; + + if (c->on_read(*ep) != 0) { + return; + } + + c->on_write(); +} +} // namespace + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + int rv; + auto c = static_cast<Client *>(w->data); + + rv = c->handle_expiry(); + if (rv != 0) { + return; + } + + c->on_write(); +} +} // namespace + +namespace { +void change_local_addrcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto c = static_cast<Client *>(w->data); + + c->change_local_addr(); +} +} // namespace + +namespace { +void key_updatecb(struct ev_loop *loop, ev_timer *w, int revents) { + auto c = static_cast<Client *>(w->data); + + if (c->initiate_key_update() != 0) { + c->disconnect(); + } +} +} // namespace + +namespace { +void delay_streamcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto c = static_cast<Client *>(w->data); + + ev_timer_stop(loop, w); + c->on_extend_max_streams(); + c->on_write(); +} +} // namespace + +namespace { +void siginthandler(struct ev_loop *loop, ev_signal *w, int revents) { + ev_break(loop, EVBREAK_ALL); +} +} // namespace + +Client::Client(struct ev_loop *loop, uint32_t client_chosen_version, + uint32_t original_version) + : remote_addr_{}, + loop_(loop), + addr_(nullptr), + port_(nullptr), + nstreams_done_(0), + nstreams_closed_(0), + nkey_update_(0), + client_chosen_version_(client_chosen_version), + original_version_(original_version), + early_data_(false), + should_exit_(false), + should_exit_on_handshake_confirmed_(false), + handshake_confirmed_(false), + tx_{} { + ev_io_init(&wev_, writecb, 0, EV_WRITE); + wev_.data = this; + ev_timer_init(&timer_, timeoutcb, 0., 0.); + timer_.data = this; + ev_timer_init(&change_local_addr_timer_, change_local_addrcb, + static_cast<double>(config.change_local_addr) / NGTCP2_SECONDS, + 0.); + change_local_addr_timer_.data = this; + ev_timer_init(&key_update_timer_, key_updatecb, + static_cast<double>(config.key_update) / NGTCP2_SECONDS, 0.); + key_update_timer_.data = this; + ev_timer_init(&delay_stream_timer_, delay_streamcb, + static_cast<double>(config.delay_stream) / NGTCP2_SECONDS, 0.); + delay_stream_timer_.data = this; + ev_signal_init(&sigintev_, siginthandler, SIGINT); +} + +Client::~Client() { disconnect(); } + +void Client::disconnect() { + tx_.send_blocked = false; + + handle_error(); + + config.tx_loss_prob = 0; + + ev_timer_stop(loop_, &delay_stream_timer_); + ev_timer_stop(loop_, &key_update_timer_); + ev_timer_stop(loop_, &change_local_addr_timer_); + ev_timer_stop(loop_, &timer_); + + ev_io_stop(loop_, &wev_); + + for (auto &ep : endpoints_) { + ev_io_stop(loop_, &ep.rev); + close(ep.fd); + } + + endpoints_.clear(); + + ev_signal_stop(loop_, &sigintev_); +} + +namespace { +int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data) { + if (!config.quiet && !config.no_quic_dump) { + debug::print_crypto_data(crypto_level, data, datalen); + } + + return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data, + datalen, user_data); +} +} // namespace + +namespace { +int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) { + if (!config.quiet && !config.no_quic_dump) { + debug::print_stream_data(stream_id, data, datalen); + } + + auto c = static_cast<Client *>(user_data); + + if (c->recv_stream_data(flags, stream_id, data, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t offset, uint64_t datalen, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + if (c->acked_stream_data_offset(stream_id, offset, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +namespace { +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (!config.quiet) { + debug::handshake_completed(conn, user_data); + } + + if (c->handshake_completed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::handshake_completed() { + if (early_data_ && !tls_session_.get_early_data_accepted()) { + if (!config.quiet) { + std::cerr << "Early data was rejected by server" << std::endl; + } + + // Some TLS backends only report early data rejection after + // handshake completion (e.g., OpenSSL). For TLS backends which + // report it early (e.g., BoringSSL and PicoTLS), the following + // functions are noop. + if (auto rv = ngtcp2_conn_early_data_rejected(conn_); rv != 0) { + std::cerr << "ngtcp2_conn_early_data_rejected: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + } + + if (!config.quiet) { + std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name() + << std::endl; + std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn() + << std::endl; + } + + if (config.tp_file) { + auto params = ngtcp2_conn_get_remote_transport_params(conn_); + + if (write_transport_params(config.tp_file, params) != 0) { + std::cerr << "Could not write transport parameters in " << config.tp_file + << std::endl; + } + } + + return 0; +} + +namespace { +int handshake_confirmed(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (!config.quiet) { + debug::handshake_confirmed(conn, user_data); + } + + if (c->handshake_confirmed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Client::handshake_confirmed() { + handshake_confirmed_ = true; + + if (config.change_local_addr) { + start_change_local_addr_timer(); + } + if (config.key_update) { + start_key_update_timer(); + } + if (config.delay_stream) { + start_delay_stream_timer(); + } + + if (should_exit_on_handshake_confirmed_) { + should_exit_ = true; + } + + return 0; +} + +namespace { +int recv_version_negotiation(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv, void *user_data) { + auto c = static_cast<Client *>(user_data); + + c->recv_version_negotiation(sv, nsv); + + return 0; +} +} // namespace + +void Client::recv_version_negotiation(const uint32_t *sv, size_t nsv) { + offered_versions_.resize(nsv); + std::copy_n(sv, nsv, std::begin(offered_versions_)); +} + +namespace { +int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + + if (c->on_stream_close(stream_id, app_error_code) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int extend_max_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, + void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (c->on_extend_max_streams() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { + auto dis = std::uniform_int_distribution<uint8_t>(0, 255); + std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); }); +} +} // namespace + +namespace { +int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, + size_t cidlen, void *user_data) { + if (util::generate_secure_random(cid->data, cidlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + cid->datalen = cidlen; + if (ngtcp2_crypto_generate_stateless_reset_token( + token, config.static_secret.data(), config.static_secret.size(), + cid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) { + if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (!config.quiet && config.show_secret) { + debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN); + } + + return 0; +} +} // namespace + +namespace { +int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen, + void *user_data) { + auto c = static_cast<Client *>(user_data); + + if (c->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx, + tx_iv, current_rx_secret, current_tx_secret, + secretlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path, + ngtcp2_path_validation_result res, void *user_data) { + if (!config.quiet) { + debug::path_validation(path, res); + } + + if (flags & NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR) { + auto c = static_cast<Client *>(user_data); + + c->set_remote_addr(path->remote); + } + + return 0; +} +} // namespace + +void Client::set_remote_addr(const ngtcp2_addr &remote_addr) { + memcpy(&remote_addr_.su, remote_addr.addr, remote_addr.addrlen); + remote_addr_.len = remote_addr.addrlen; +} + +namespace { +int select_preferred_address(ngtcp2_conn *conn, ngtcp2_path *dest, + const ngtcp2_preferred_addr *paddr, + void *user_data) { + auto c = static_cast<Client *>(user_data); + Address remote_addr; + + if (config.no_preferred_addr) { + return 0; + } + + if (c->select_preferred_address(remote_addr, paddr) != 0) { + return 0; + } + + auto ep = c->endpoint_for(remote_addr); + if (!ep) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + ngtcp2_addr_copy_byte(&dest->local, &(*ep)->addr.su.sa, (*ep)->addr.len); + ngtcp2_addr_copy_byte(&dest->remote, &remote_addr.su.sa, remote_addr.len); + dest->user_data = *ep; + + return 0; +} +} // namespace + +namespace { +int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data) { + auto c = static_cast<Client *>(user_data); + if (c->extend_max_stream_data(stream_id, max_data) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Client::extend_max_stream_data(int64_t stream_id, uint64_t max_data) { + auto it = streams_.find(stream_id); + assert(it != std::end(streams_)); + auto &stream = (*it).second; + + if (nghttp3_buf_len(&stream->reqbuf)) { + sendq_.emplace(stream.get()); + } + + return 0; +} + +namespace { +int recv_new_token(ngtcp2_conn *conn, const ngtcp2_vec *token, + void *user_data) { + if (config.token_file.empty()) { + return 0; + } + + auto f = BIO_new_file(config.token_file.data(), "w"); + if (f == nullptr) { + std::cerr << "Could not write token in " << config.token_file << std::endl; + return 0; + } + + PEM_write_bio(f, "QUIC TOKEN", "", token->base, token->len); + BIO_free(f); + + return 0; +} +} // namespace + +namespace { +int early_data_rejected(ngtcp2_conn *conn, void *user_data) { + auto c = static_cast<Client *>(user_data); + + c->early_data_rejected(); + + return 0; +} +} // namespace + +void Client::early_data_rejected() { + nstreams_done_ = 0; + streams_.clear(); +} + +int Client::init(int fd, const Address &local_addr, const Address &remote_addr, + const char *addr, const char *port, + TLSClientContext &tls_ctx) { + endpoints_.reserve(4); + + endpoints_.emplace_back(); + auto &ep = endpoints_.back(); + ep.addr = local_addr; + ep.client = this; + ep.fd = fd; + ev_io_init(&ep.rev, readcb, fd, EV_READ); + ep.rev.data = &ep; + + remote_addr_ = remote_addr; + addr_ = addr; + port_ = port; + + auto callbacks = ngtcp2_callbacks{ + ngtcp2_crypto_client_initial_cb, + nullptr, // recv_client_initial + ::recv_crypto_data, + ::handshake_completed, + ::recv_version_negotiation, + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + do_hp_mask, + ::recv_stream_data, + ::acked_stream_data_offset, + nullptr, // stream_open + stream_close, + nullptr, // recv_stateless_reset + ngtcp2_crypto_recv_retry_cb, + extend_max_streams_bidi, + nullptr, // extend_max_streams_uni + rand, + get_new_connection_id, + nullptr, // remove_connection_id + ::update_key, + path_validation, + ::select_preferred_address, + nullptr, // stream_reset + nullptr, // extend_max_remote_streams_bidi, + nullptr, // extend_max_remote_streams_uni, + ::extend_max_stream_data, + nullptr, // dcid_status + ::handshake_confirmed, + ::recv_new_token, + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + nullptr, // recv_datagram + nullptr, // ack_datagram + nullptr, // lost_datagram + ngtcp2_crypto_get_path_challenge_data_cb, + nullptr, // stream_stop_sending + ngtcp2_crypto_version_negotiation_cb, + nullptr, // recv_rx_key + nullptr, // recv_tx_key + ::early_data_rejected, + }; + + ngtcp2_cid scid, dcid; + scid.datalen = 17; + if (util::generate_secure_random(scid.data, scid.datalen) != 0) { + std::cerr << "Could not generate source connection ID" << std::endl; + return -1; + } + if (config.dcid.datalen == 0) { + dcid.datalen = 18; + if (util::generate_secure_random(dcid.data, dcid.datalen) != 0) { + std::cerr << "Could not generate destination connection ID" << std::endl; + return -1; + } + } else { + dcid = config.dcid; + } + + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + settings.log_printf = config.quiet ? nullptr : debug::log_printf; + if (!config.qlog_file.empty() || !config.qlog_dir.empty()) { + std::string path; + if (!config.qlog_file.empty()) { + path = config.qlog_file; + } else { + path = std::string{config.qlog_dir}; + path += '/'; + path += util::format_hex(scid.data, scid.datalen); + path += ".sqlog"; + } + qlog_ = fopen(path.c_str(), "w"); + if (qlog_ == nullptr) { + std::cerr << "Could not open qlog file " << std::quoted(path) << ": " + << strerror(errno) << std::endl; + return -1; + } + settings.qlog.write = qlog_write_cb; + } + + settings.cc_algo = config.cc_algo; + settings.initial_ts = util::timestamp(loop_); + settings.initial_rtt = config.initial_rtt; + settings.max_window = config.max_window; + settings.max_stream_window = config.max_stream_window; + if (config.max_udp_payload_size) { + settings.max_tx_udp_payload_size = config.max_udp_payload_size; + settings.no_tx_udp_payload_size_shaping = 1; + } + settings.handshake_timeout = config.handshake_timeout; + settings.no_pmtud = config.no_pmtud; + settings.ack_thresh = config.ack_thresh; + + std::string token; + + if (!config.token_file.empty()) { + std::cerr << "Reading token file " << config.token_file << std::endl; + + auto t = util::read_token(config.token_file); + if (t) { + token = std::move(*t); + settings.token.base = reinterpret_cast<uint8_t *>(token.data()); + settings.token.len = token.size(); + } + } + + if (!config.other_versions.empty()) { + settings.other_versions = config.other_versions.data(); + settings.other_versionslen = config.other_versions.size(); + } + + if (!config.preferred_versions.empty()) { + settings.preferred_versions = config.preferred_versions.data(); + settings.preferred_versionslen = config.preferred_versions.size(); + } + + settings.original_version = original_version_; + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; + params.initial_max_stream_data_bidi_remote = + config.max_stream_data_bidi_remote; + params.initial_max_stream_data_uni = config.max_stream_data_uni; + params.initial_max_data = config.max_data; + params.initial_max_streams_bidi = config.max_streams_bidi; + params.initial_max_streams_uni = 0; + params.max_idle_timeout = config.timeout; + params.active_connection_id_limit = 7; + + auto path = ngtcp2_path{ + { + const_cast<sockaddr *>(&ep.addr.su.sa), + ep.addr.len, + }, + { + const_cast<sockaddr *>(&remote_addr.su.sa), + remote_addr.len, + }, + &ep, + }; + auto rv = ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path, + client_chosen_version_, &callbacks, + &settings, ¶ms, nullptr, this); + + if (rv != 0) { + std::cerr << "ngtcp2_conn_client_new: " << ngtcp2_strerror(rv) << std::endl; + return -1; + } + + if (tls_session_.init(early_data_, tls_ctx, addr_, this, + client_chosen_version_, AppProtocol::HQ) != 0) { + return -1; + } + + ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle()); + + if (early_data_ && config.tp_file) { + ngtcp2_transport_params params; + if (read_transport_params(config.tp_file, ¶ms) != 0) { + std::cerr << "Could not read transport parameters from " << config.tp_file + << std::endl; + early_data_ = false; + } else { + ngtcp2_conn_set_early_remote_transport_params(conn_, ¶ms); + if (make_stream_early() != 0) { + return -1; + } + } + } + + ev_io_start(loop_, &ep.rev); + + ev_signal_start(loop_, &sigintev_); + + return 0; +} + +int Client::feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen, + const ngtcp2_pkt_info *pi, uint8_t *data, + size_t datalen) { + auto path = ngtcp2_path{ + { + const_cast<sockaddr *>(&ep.addr.su.sa), + ep.addr.len, + }, + { + const_cast<sockaddr *>(sa), + salen, + }, + const_cast<Endpoint *>(&ep), + }; + if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen, + util::timestamp(loop_)); + rv != 0) { + std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; + if (!last_error_.error_code) { + if (rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_connection_close_error_set_transport_error_tls_alert( + &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0); + } else { + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, rv, nullptr, 0); + } + } + disconnect(); + return -1; + } + return 0; +} + +int Client::on_read(const Endpoint &ep) { + std::array<uint8_t, 64_k> buf; + sockaddr_union su; + size_t pktcnt = 0; + ngtcp2_pkt_info pi; + + iovec msg_iov; + msg_iov.iov_base = buf.data(); + msg_iov.iov_len = buf.size(); + + msghdr msg{}; + msg.msg_name = &su; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint8_t))]; + msg.msg_control = msg_ctrl; + + for (;;) { + msg.msg_namelen = sizeof(su); + msg.msg_controllen = sizeof(msg_ctrl); + + auto nread = recvmsg(ep.fd, &msg, 0); + + if (nread == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + std::cerr << "recvmsg: " << strerror(errno) << std::endl; + } + break; + } + + pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family); + + if (!config.quiet) { + std::cerr << "Received packet: local=" + << util::straddr(&ep.addr.su.sa, ep.addr.len) + << " remote=" << util::straddr(&su.sa, msg.msg_namelen) + << " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread + << " bytes" << std::endl; + } + + if (debug::packet_lost(config.rx_loss_prob)) { + if (!config.quiet) { + std::cerr << "** Simulated incoming packet loss **" << std::endl; + } + break; + } + + if (feed_data(ep, &su.sa, msg.msg_namelen, &pi, buf.data(), nread) != 0) { + return -1; + } + + if (++pktcnt >= 10) { + break; + } + } + + if (should_exit_) { + disconnect(); + return -1; + } + + update_timer(); + + return 0; +} + +int Client::handle_expiry() { + auto now = util::timestamp(loop_); + if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) { + std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_transport_error_liberr(&last_error_, rv, + nullptr, 0); + disconnect(); + return -1; + } + + return 0; +} + +int Client::on_write() { + if (tx_.send_blocked) { + if (auto rv = send_blocked_packet(); rv != 0) { + return rv; + } + + if (tx_.send_blocked) { + return 0; + } + } + + if (auto rv = write_streams(); rv != 0) { + return rv; + } + + if (should_exit_) { + disconnect(); + return -1; + } + + update_timer(); + return 0; +} + +int Client::write_streams() { + ngtcp2_vec vec; + ngtcp2_path_storage ps; + size_t pktcnt = 0; + auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_); + auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size; + auto ts = util::timestamp(loop_); + + ngtcp2_path_storage_zero(&ps); + + for (;;) { + int64_t stream_id = -1; + size_t vcnt = 0; + uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + Stream *stream = nullptr; + + if (!sendq_.empty() && ngtcp2_conn_get_max_data_left(conn_)) { + stream = *std::begin(sendq_); + + stream_id = stream->stream_id; + vec.base = stream->reqbuf.pos; + vec.len = nghttp3_buf_len(&stream->reqbuf); + vcnt = 1; + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + + ngtcp2_ssize ndatalen; + ngtcp2_pkt_info pi; + + auto nwrite = ngtcp2_conn_writev_stream( + conn_, &ps.path, &pi, tx_.data.data(), max_udp_payload_size, &ndatalen, + flags, stream_id, &vec, vcnt, ts); + if (nwrite < 0) { + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + case NGTCP2_ERR_STREAM_SHUT_WR: + assert(ndatalen == -1); + sendq_.erase(std::begin(sendq_)); + continue; + case NGTCP2_ERR_WRITE_MORE: + assert(ndatalen >= 0); + stream->reqbuf.pos += ndatalen; + if (nghttp3_buf_len(&stream->reqbuf) == 0) { + sendq_.erase(std::begin(sendq_)); + } + continue; + } + + assert(ndatalen == -1); + + std::cerr << "ngtcp2_conn_write_stream: " << ngtcp2_strerror(nwrite) + << std::endl; + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, nwrite, nullptr, 0); + disconnect(); + return -1; + } else if (ndatalen >= 0) { + stream->reqbuf.pos += ndatalen; + if (nghttp3_buf_len(&stream->reqbuf) == 0) { + sendq_.erase(std::begin(sendq_)); + } + } + + if (nwrite == 0) { + // We are congestion limited. + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + ev_io_stop(loop_, &wev_); + return 0; + } + + auto &ep = *static_cast<Endpoint *>(ps.path.user_data); + + if (auto rv = + send_packet(ep, ps.path.remote, pi.ecn, tx_.data.data(), nwrite); + rv != NETWORK_ERR_OK) { + if (rv != NETWORK_ERR_SEND_BLOCKED) { + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0); + disconnect(); + + return rv; + } + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + on_send_blocked(ep, ps.path.remote, pi.ecn, nwrite); + + return 0; + } + + if (++pktcnt == max_pktcnt) { + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + start_wev_endpoint(ep); + return 0; + } + } +} + +void Client::update_timer() { + auto expiry = ngtcp2_conn_get_expiry(conn_); + auto now = util::timestamp(loop_); + + if (expiry <= now) { + if (!config.quiet) { + auto t = static_cast<ev_tstamp>(now - expiry) / NGTCP2_SECONDS; + std::cerr << "Timer has already expired: " << t << "s" << std::endl; + } + + ev_feed_event(loop_, &timer_, EV_TIMER); + + return; + } + + auto t = static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS; + if (!config.quiet) { + std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat + << std::endl; + } + timer_.repeat = t; + ev_timer_again(loop_, &timer_); +} + +#ifdef HAVE_LINUX_RTNETLINK_H +namespace { +int bind_addr(Address &local_addr, int fd, const in_addr_union *iau, + int family) { + addrinfo hints{}; + addrinfo *res, *rp; + + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + char *node; + std::array<char, NI_MAXHOST> nodebuf; + + if (iau) { + if (inet_ntop(family, iau, nodebuf.data(), nodebuf.size()) == nullptr) { + std::cerr << "inet_ntop: " << strerror(errno) << std::endl; + return -1; + } + + node = nodebuf.data(); + } else { + node = nullptr; + } + + if (auto rv = getaddrinfo(node, "0", &hints, &res); rv != 0) { + std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + for (rp = res; rp; rp = rp->ai_next) { + if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; + } + } + + if (!rp) { + std::cerr << "Could not bind" << std::endl; + return -1; + } + + socklen_t len = sizeof(local_addr.su.storage); + if (getsockname(fd, &local_addr.su.sa, &len) == -1) { + std::cerr << "getsockname: " << strerror(errno) << std::endl; + return -1; + } + local_addr.len = len; + local_addr.ifindex = 0; + + return 0; +} +} // namespace +#endif // HAVE_LINUX_RTNETLINK_H + +#ifndef HAVE_LINUX_RTNETLINK_H +namespace { +int connect_sock(Address &local_addr, int fd, const Address &remote_addr) { + if (connect(fd, &remote_addr.su.sa, remote_addr.len) != 0) { + std::cerr << "connect: " << strerror(errno) << std::endl; + return -1; + } + + socklen_t len = sizeof(local_addr.su.storage); + if (getsockname(fd, &local_addr.su.sa, &len) == -1) { + std::cerr << "getsockname: " << strerror(errno) << std::endl; + return -1; + } + local_addr.len = len; + local_addr.ifindex = 0; + + return 0; +} +} // namespace +#endif // !HAVE_LINUX_RTNETLINK_H + +namespace { +int udp_sock(int family) { + auto fd = util::create_nonblock_socket(family, SOCK_DGRAM, IPPROTO_UDP); + if (fd == -1) { + return -1; + } + + fd_set_recv_ecn(fd, family); + fd_set_ip_mtu_discover(fd, family); + fd_set_ip_dontfrag(fd, family); + + return fd; +} +} // namespace + +namespace { +int create_sock(Address &remote_addr, const char *addr, const char *port) { + addrinfo hints{}; + addrinfo *res, *rp; + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) { + std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + int fd = -1; + + for (rp = res; rp; rp = rp->ai_next) { + fd = udp_sock(rp->ai_family); + if (fd == -1) { + continue; + } + + break; + } + + if (!rp) { + std::cerr << "Could not create socket" << std::endl; + return -1; + } + + remote_addr.len = rp->ai_addrlen; + memcpy(&remote_addr.su, rp->ai_addr, rp->ai_addrlen); + + return fd; +} +} // namespace + +std::optional<Endpoint *> Client::endpoint_for(const Address &remote_addr) { +#ifdef HAVE_LINUX_RTNETLINK_H + in_addr_union iau; + + if (get_local_addr(iau, remote_addr) != 0) { + std::cerr << "Could not get local address for a selected preferred address" + << std::endl; + return nullptr; + } + + auto current_path = ngtcp2_conn_get_path(conn_); + auto current_ep = static_cast<Endpoint *>(current_path->user_data); + if (addreq(¤t_ep->addr.su.sa, iau)) { + return current_ep; + } +#endif // HAVE_LINUX_RTNETLINK_H + + auto fd = udp_sock(remote_addr.su.sa.sa_family); + if (fd == -1) { + return nullptr; + } + + Address local_addr; + +#ifdef HAVE_LINUX_RTNETLINK_H + if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) { + close(fd); + return nullptr; + } +#else // !HAVE_LINUX_RTNETLINK_H + if (connect_sock(local_addr, fd, remote_addr) != 0) { + close(fd); + return nullptr; + } +#endif // !HAVE_LINUX_RTNETLINK_H + + endpoints_.emplace_back(); + auto &ep = endpoints_.back(); + ep.addr = local_addr; + ep.client = this; + ep.fd = fd; + ev_io_init(&ep.rev, readcb, fd, EV_READ); + ep.rev.data = &ep; + + ev_io_start(loop_, &ep.rev); + + return &ep; +} + +void Client::start_change_local_addr_timer() { + ev_timer_start(loop_, &change_local_addr_timer_); +} + +int Client::change_local_addr() { + Address local_addr; + + if (!config.quiet) { + std::cerr << "Changing local address" << std::endl; + } + + auto nfd = udp_sock(remote_addr_.su.sa.sa_family); + if (nfd == -1) { + return -1; + } + +#ifdef HAVE_LINUX_RTNETLINK_H + in_addr_union iau; + + if (get_local_addr(iau, remote_addr_) != 0) { + std::cerr << "Could not get local address" << std::endl; + close(nfd); + return -1; + } + + if (bind_addr(local_addr, nfd, &iau, remote_addr_.su.sa.sa_family) != 0) { + close(nfd); + return -1; + } +#else // !HAVE_LINUX_RTNETLINK_H + if (connect_sock(local_addr, nfd, remote_addr_) != 0) { + close(nfd); + return -1; + } +#endif // !HAVE_LINUX_RTNETLINK_H + + if (!config.quiet) { + std::cerr << "Local address is now " + << util::straddr(&local_addr.su.sa, local_addr.len) << std::endl; + } + + endpoints_.emplace_back(); + auto &ep = endpoints_.back(); + ep.addr = local_addr; + ep.client = this; + ep.fd = nfd; + ev_io_init(&ep.rev, readcb, nfd, EV_READ); + ep.rev.data = &ep; + + ngtcp2_addr addr; + ngtcp2_addr_init(&addr, &local_addr.su.sa, local_addr.len); + + if (config.nat_rebinding) { + ngtcp2_conn_set_local_addr(conn_, &addr); + ngtcp2_conn_set_path_user_data(conn_, &ep); + } else { + auto path = ngtcp2_path{ + addr, + { + const_cast<sockaddr *>(&remote_addr_.su.sa), + remote_addr_.len, + }, + &ep, + }; + if (auto rv = ngtcp2_conn_initiate_immediate_migration( + conn_, &path, util::timestamp(loop_)); + rv != 0) { + std::cerr << "ngtcp2_conn_initiate_immediate_migration: " + << ngtcp2_strerror(rv) << std::endl; + } + } + + ev_io_start(loop_, &ep.rev); + + return 0; +} + +void Client::start_key_update_timer() { + ev_timer_start(loop_, &key_update_timer_); +} + +int Client::update_key(uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen) { + if (!config.quiet) { + std::cerr << "Updating traffic key" << std::endl; + } + + auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_); + auto aead = &crypto_ctx->aead; + auto keylen = ngtcp2_crypto_aead_keylen(aead); + auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + + ++nkey_update_; + + std::array<uint8_t, 64> rx_key, tx_key; + + if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx, + rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(), + tx_iv, current_rx_secret, current_tx_secret, + secretlen) != 0) { + return -1; + } + + if (!config.quiet && config.show_secret) { + std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl; + debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv, + ivlen); + std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl; + debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv, + ivlen); + } + + return 0; +} + +int Client::initiate_key_update() { + if (!config.quiet) { + std::cerr << "Initiate key update" << std::endl; + } + + if (auto rv = ngtcp2_conn_initiate_key_update(conn_, util::timestamp(loop_)); + rv != 0) { + std::cerr << "ngtcp2_conn_initiate_key_update: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +void Client::start_delay_stream_timer() { + ev_timer_start(loop_, &delay_stream_timer_); +} + +int Client::send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr, + unsigned int ecn, const uint8_t *data, size_t datalen) { + if (debug::packet_lost(config.tx_loss_prob)) { + if (!config.quiet) { + std::cerr << "** Simulated outgoing packet loss **" << std::endl; + } + return NETWORK_ERR_OK; + } + + iovec msg_iov; + msg_iov.iov_base = const_cast<uint8_t *>(data); + msg_iov.iov_len = datalen; + + msghdr msg{}; +#ifdef HAVE_LINUX_RTNETLINK_H + msg.msg_name = const_cast<sockaddr *>(remote_addr.addr); + msg.msg_namelen = remote_addr.addrlen; +#endif // HAVE_LINUX_RTNETLINK_H + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + fd_set_ecn(ep.fd, remote_addr.addr->sa_family, ecn); + + ssize_t nwrite = 0; + + do { + nwrite = sendmsg(ep.fd, &msg, 0); + } while (nwrite == -1 && errno == EINTR); + + if (nwrite == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return NETWORK_ERR_SEND_BLOCKED; + } + std::cerr << "sendmsg: " << strerror(errno) << std::endl; + if (errno == EMSGSIZE) { + return 0; + } + return NETWORK_ERR_FATAL; + } + + assert(static_cast<size_t>(nwrite) == datalen); + + if (!config.quiet) { + std::cerr << "Sent packet: local=" + << util::straddr(&ep.addr.su.sa, ep.addr.len) << " remote=" + << util::straddr(remote_addr.addr, remote_addr.addrlen) + << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite + << " bytes" << std::endl; + } + + return NETWORK_ERR_OK; +} + +void Client::on_send_blocked(const Endpoint &ep, const ngtcp2_addr &remote_addr, + unsigned int ecn, size_t datalen) { + assert(!tx_.send_blocked); + + tx_.send_blocked = true; + + memcpy(&tx_.blocked.remote_addr.su, remote_addr.addr, remote_addr.addrlen); + tx_.blocked.remote_addr.len = remote_addr.addrlen; + tx_.blocked.ecn = ecn; + tx_.blocked.datalen = datalen; + tx_.blocked.endpoint = &ep; + + start_wev_endpoint(ep); +} + +void Client::start_wev_endpoint(const Endpoint &ep) { + // We do not close ep.fd, so we can expect that each Endpoint has + // unique fd. + if (ep.fd != wev_.fd) { + if (ev_is_active(&wev_)) { + ev_io_stop(loop_, &wev_); + } + + ev_io_set(&wev_, ep.fd, EV_WRITE); + } + + ev_io_start(loop_, &wev_); +} + +int Client::send_blocked_packet() { + assert(tx_.send_blocked); + + ngtcp2_addr remote_addr{ + .addr = &tx_.blocked.remote_addr.su.sa, + .addrlen = tx_.blocked.remote_addr.len, + }; + + auto rv = send_packet(*tx_.blocked.endpoint, remote_addr, tx_.blocked.ecn, + tx_.data.data(), tx_.blocked.datalen); + if (rv != 0) { + if (rv == NETWORK_ERR_SEND_BLOCKED) { + assert(wev_.fd == tx_.blocked.endpoint->fd); + + ev_io_start(loop_, &wev_); + + return 0; + } + + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0); + disconnect(); + + return rv; + } + + tx_.send_blocked = false; + + return 0; +} + +int Client::handle_error() { + if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_) || + ngtcp2_conn_is_in_draining_period(conn_)) { + return 0; + } + + std::array<uint8_t, NGTCP2_MAX_UDP_PAYLOAD_SIZE> buf; + + ngtcp2_path_storage ps; + + ngtcp2_path_storage_zero(&ps); + + ngtcp2_pkt_info pi; + + auto nwrite = ngtcp2_conn_write_connection_close( + conn_, &ps.path, &pi, buf.data(), buf.size(), &last_error_, + util::timestamp(loop_)); + if (nwrite < 0) { + std::cerr << "ngtcp2_conn_write_connection_close: " + << ngtcp2_strerror(nwrite) << std::endl; + return -1; + } + + if (nwrite == 0) { + return 0; + } + + return send_packet(*static_cast<Endpoint *>(ps.path.user_data), + ps.path.remote, pi.ecn, buf.data(), nwrite); +} + +int Client::on_stream_close(int64_t stream_id, uint64_t app_error_code) { + auto it = streams_.find(stream_id); + assert(it != std::end(streams_)); + auto &stream = (*it).second; + + sendq_.erase(stream.get()); + + ++nstreams_closed_; + + if (config.exit_on_first_stream_close || + (config.exit_on_all_streams_close && config.nstreams == nstreams_done_ && + nstreams_closed_ == nstreams_done_)) { + if (handshake_confirmed_) { + should_exit_ = true; + } else { + should_exit_on_handshake_confirmed_ = true; + } + } + + if (!ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(conn_, stream_id)); + ngtcp2_conn_extend_max_streams_uni(conn_, 1); + } + + if (!config.quiet) { + std::cerr << "HTTP stream " << stream_id << " closed with error code " + << app_error_code << std::endl; + } + streams_.erase(it); + + return 0; +} + +int Client::make_stream_early() { return on_extend_max_streams(); } + +int Client::on_extend_max_streams() { + int64_t stream_id; + + if ((config.delay_stream && !handshake_confirmed_) || + ev_is_active(&delay_stream_timer_)) { + return 0; + } + + for (; nstreams_done_ < config.nstreams; ++nstreams_done_) { + if (auto rv = ngtcp2_conn_open_bidi_stream(conn_, &stream_id, nullptr); + rv != 0) { + assert(NGTCP2_ERR_STREAM_ID_BLOCKED == rv); + break; + } + + auto stream = std::make_unique<Stream>( + config.requests[nstreams_done_ % config.requests.size()], stream_id); + + if (submit_http_request(stream.get()) != 0) { + break; + } + + if (!config.download.empty()) { + stream->open_file(stream->req.path); + } + streams_.emplace(stream_id, std::move(stream)); + } + return 0; +} + +int Client::submit_http_request(Stream *stream) { + const auto &req = stream->req; + + stream->rawreqbuf = config.http_method; + stream->rawreqbuf += ' '; + stream->rawreqbuf += req.path; + stream->rawreqbuf += "\r\n"; + + nghttp3_buf_init(&stream->reqbuf); + stream->reqbuf.begin = reinterpret_cast<uint8_t *>(stream->rawreqbuf.data()); + stream->reqbuf.pos = stream->reqbuf.begin; + stream->reqbuf.end = stream->reqbuf.last = + stream->reqbuf.begin + stream->rawreqbuf.size(); + + if (!config.quiet) { + auto nva = std::array<nghttp3_nv, 2>{ + util::make_nv_nn(":method", config.http_method), + util::make_nv_nn(":path", req.path), + }; + debug::print_http_request_headers(stream->stream_id, nva.data(), + nva.size()); + } + + sendq_.emplace(stream); + + return 0; +} + +int Client::recv_stream_data(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + auto it = streams_.find(stream_id); + assert(it != std::end(streams_)); + auto &stream = (*it).second; + + ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, datalen); + ngtcp2_conn_extend_max_offset(conn_, datalen); + + if (stream->fd == -1) { + return 0; + } + + ssize_t nwrite; + do { + nwrite = write(stream->fd, data, datalen); + } while (nwrite == -1 && errno == EINTR); + + return 0; +} + +int Client::acked_stream_data_offset(int64_t stream_id, uint64_t offset, + uint64_t datalen) { + auto it = streams_.find(stream_id); + assert(it != std::end(streams_)); + auto &stream = (*it).second; + (void)stream; + assert(static_cast<uint64_t>(stream->reqbuf.end - stream->reqbuf.begin) >= + offset + datalen); + return 0; +} + +int Client::select_preferred_address(Address &selected_addr, + const ngtcp2_preferred_addr *paddr) { + auto path = ngtcp2_conn_get_path(conn_); + + switch (path->local.addr->sa_family) { + case AF_INET: + if (!paddr->ipv4_present) { + return -1; + } + selected_addr.su.in = paddr->ipv4; + selected_addr.len = sizeof(paddr->ipv4); + break; + case AF_INET6: + if (!paddr->ipv6_present) { + return -1; + } + selected_addr.su.in6 = paddr->ipv6; + selected_addr.len = sizeof(paddr->ipv6); + break; + default: + return -1; + } + + char host[NI_MAXHOST], service[NI_MAXSERV]; + if (auto rv = getnameinfo(&selected_addr.su.sa, selected_addr.len, host, + sizeof(host), service, sizeof(service), + NI_NUMERICHOST | NI_NUMERICSERV); + rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + if (!config.quiet) { + std::cerr << "selected server preferred_address is [" << host + << "]:" << service << std::endl; + } + + return 0; +} + +const std::vector<uint32_t> &Client::get_offered_versions() const { + return offered_versions_; +} + +namespace { +int run(Client &c, const char *addr, const char *port, + TLSClientContext &tls_ctx) { + Address remote_addr, local_addr; + + auto fd = create_sock(remote_addr, addr, port); + if (fd == -1) { + return -1; + } + +#ifdef HAVE_LINUX_RTNETLINK_H + in_addr_union iau; + + if (get_local_addr(iau, remote_addr) != 0) { + std::cerr << "Could not get local address" << std::endl; + close(fd); + return -1; + } + + if (bind_addr(local_addr, fd, &iau, remote_addr.su.sa.sa_family) != 0) { + close(fd); + return -1; + } +#else // !HAVE_LINUX_RTNETLINK_H + if (connect_sock(local_addr, fd, remote_addr) != 0) { + close(fd); + return -1; + } +#endif // !HAVE_LINUX_RTNETLINK_H + + if (c.init(fd, local_addr, remote_addr, addr, port, tls_ctx) != 0) { + return -1; + } + + // TODO Do we need this ? + if (auto rv = c.on_write(); rv != 0) { + return rv; + } + + ev_run(EV_DEFAULT, 0); + + return 0; +} +} // namespace + +namespace { +std::string_view get_string(const char *uri, const http_parser_url &u, + http_parser_url_fields f) { + auto p = &u.field_data[f]; + return {uri + p->off, p->len}; +} +} // namespace + +namespace { +int parse_uri(Request &req, const char *uri) { + http_parser_url u; + + http_parser_url_init(&u); + if (http_parser_parse_url(uri, strlen(uri), /* is_connect = */ 0, &u) != 0) { + return -1; + } + + if (!(u.field_set & (1 << UF_SCHEMA)) || !(u.field_set & (1 << UF_HOST))) { + return -1; + } + + req.scheme = get_string(uri, u, UF_SCHEMA); + + req.authority = get_string(uri, u, UF_HOST); + if (util::numeric_host(req.authority.c_str(), AF_INET6)) { + req.authority = '[' + req.authority + ']'; + } + if (u.field_set & (1 << UF_PORT)) { + req.authority += ':'; + req.authority += get_string(uri, u, UF_PORT); + } + + if (u.field_set & (1 << UF_PATH)) { + req.path = get_string(uri, u, UF_PATH); + } else { + req.path = "/"; + } + + if (u.field_set & (1 << UF_QUERY)) { + req.path += '?'; + req.path += get_string(uri, u, UF_QUERY); + } + + return 0; +} +} // namespace + +namespace { +int parse_requests(char **argv, size_t argvlen) { + for (size_t i = 0; i < argvlen; ++i) { + auto uri = argv[i]; + Request req; + if (parse_uri(req, uri) != 0) { + std::cerr << "Could not parse URI: " << uri << std::endl; + return -1; + } + config.requests.emplace_back(std::move(req)); + } + return 0; +} +} // namespace + +std::ofstream keylog_file; + +namespace { +void print_usage() { + std::cerr << "Usage: h09client [OPTIONS] <HOST> <PORT> [<URI>...]" + << std::endl; +} +} // namespace + +namespace { +void config_set_default(Config &config) { + config = Config{}; + config.tx_loss_prob = 0.; + config.rx_loss_prob = 0.; + config.fd = -1; + config.ciphers = util::crypto_default_ciphers(); + config.groups = util::crypto_default_groups(); + config.nstreams = 0; + config.data = nullptr; + config.datalen = 0; + config.version = NGTCP2_PROTO_VER_V1; + config.timeout = 30 * NGTCP2_SECONDS; + config.http_method = "GET"sv; + config.max_data = 15_m; + config.max_stream_data_bidi_local = 6_m; + config.max_stream_data_bidi_remote = 6_m; + config.max_stream_data_uni = 6_m; + config.max_window = 24_m; + config.max_stream_window = 16_m; + config.max_streams_uni = 100; + config.cc_algo = NGTCP2_CC_ALGO_CUBIC; + config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; + config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT; + config.ack_thresh = 2; +} +} // namespace + +namespace { +void print_help() { + print_usage(); + + config_set_default(config); + + std::cout << R"( + <HOST> Remote server host (DNS name or IP address). In case of + DNS name, it will be sent in TLS SNI extension. + <PORT> Remote server port + <URI> Remote URI +Options: + -t, --tx-loss=<P> + The probability of losing outgoing packets. <P> must be + [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 + means 100% packet loss. + -r, --rx-loss=<P> + The probability of losing incoming packets. <P> must be + [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 + means 100% packet loss. + -d, --data=<PATH> + Read data from <PATH>, and send them as STREAM data. + -n, --nstreams=<N> + The number of requests. <URI>s are used in the order of + appearance in the command-line. If the number of <URI> + list is less than <N>, <URI> list is wrapped. It + defaults to 0 which means the number of <URI> specified. + -v, --version=<HEX> + Specify QUIC version to use in hex string. If the given + version is not supported by libngtcp2, client will use + QUIC v1 long packet types. Instead of specifying hex + string, there are special aliases available: "v1" + indicates QUIC v1, and "v2draft" indicates QUIC v2 + draft. + Default: )" + << std::hex << "0x" << config.version << std::dec << R"( + --preferred-versions=<HEX>[[,<HEX>]...] + Specify QUIC versions in hex string in the order of + preference. Client chooses one of those versions if + client received Version Negotiation packet from server. + These versions must be supported by libngtcp2. Instead + of specifying hex string, there are special aliases + available: "v1" indicates QUIC v1, and "v2draft" + indicates QUIC v2 draft. + --other-versions=<HEX>[[,<HEX>]...] + Specify QUIC versions in hex string that are sent in + other_versions field of version_information transport + parameter. This list can include a version which is not + supported by libngtcp2. Instead of specifying hex + string, there are special aliases available: "v1" + indicates QUIC v1, and "v2draft" indicates QUIC v2 + draft. + -q, --quiet Suppress debug output. + -s, --show-secret + Print out secrets unless --quiet is used. + --timeout=<DURATION> + Specify idle timeout. + Default: )" + << util::format_duration(config.timeout) << R"( + --ciphers=<CIPHERS> + Specify the cipher suite list to enable. + Default: )" + << config.ciphers << R"( + --groups=<GROUPS> + Specify the supported groups. + Default: )" + << config.groups << R"( + --session-file=<PATH> + Read/write TLS session from/to <PATH>. To resume a + session, the previous session must be supplied with this + option. + --tp-file=<PATH> + Read/write QUIC transport parameters from/to <PATH>. To + send 0-RTT data, the transport parameters received from + the previous session must be supplied with this option. + --dcid=<DCID> + Specify initial DCID. <DCID> is hex string. When + decoded as binary, it should be at least 8 bytes and at + most 18 bytes long. + --change-local-addr=<DURATION> + Client changes local address when <DURATION> elapse + after handshake completes. + --nat-rebinding + When used with --change-local-addr, simulate NAT + rebinding. In other words, client changes local + address, but it does not start path validation. + --key-update=<DURATION> + Client initiates key update when <DURATION> elapse after + handshake completes. + -m, --http-method=<METHOD> + Specify HTTP method. Default: )" + << config.http_method << R"( + --delay-stream=<DURATION> + Delay sending STREAM data in 1-RTT for <DURATION> after + handshake completes. + --no-preferred-addr + Do not try to use preferred address offered by server. + --key=<PATH> + The path to client private key PEM file. + --cert=<PATH> + The path to client certificate PEM file. + --download=<PATH> + The path to the directory to save a downloaded content. + It is undefined if 2 concurrent requests write to the + same file. If a request path does not contain a path + component usable as a file name, it defaults to + "index.html". + --no-quic-dump + Disables printing QUIC STREAM and CRYPTO frame data out. + --no-http-dump + Disables printing HTTP response body out. + --qlog-file=<PATH> + The path to write qlog. This option and --qlog-dir are + mutually exclusive. + --qlog-dir=<PATH> + Path to the directory where qlog file is stored. The + file name of each qlog is the Source Connection ID of + client. This option and --qlog-file are mutually + exclusive. + --max-data=<SIZE> + The initial connection-level flow control window. + Default: )" + << util::format_uint_iec(config.max_data) << R"( + --max-stream-data-bidi-local=<SIZE> + The initial stream-level flow control window for a + bidirectional stream that the local endpoint initiates. + Default: )" + << util::format_uint_iec(config.max_stream_data_bidi_local) << R"( + --max-stream-data-bidi-remote=<SIZE> + The initial stream-level flow control window for a + bidirectional stream that the remote endpoint initiates. + Default: )" + << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"( + --max-stream-data-uni=<SIZE> + The initial stream-level flow control window for a + unidirectional stream. + Default: )" + << util::format_uint_iec(config.max_stream_data_uni) << R"( + --max-streams-bidi=<N> + The number of the concurrent bidirectional streams. + Default: )" + << config.max_streams_bidi << R"( + --max-streams-uni=<N> + The number of the concurrent unidirectional streams. + Default: )" + << config.max_streams_uni << R"( + --exit-on-first-stream-close + Exit when a first HTTP stream is closed. + --exit-on-all-streams-close + Exit when all HTTP streams are closed. + --disable-early-data + Disable early data. + --cc=(cubic|reno|bbr|bbr2) + The name of congestion controller algorithm. + Default: )" + << util::strccalgo(config.cc_algo) << R"( + --token-file=<PATH> + Read/write token from/to <PATH>. Token is obtained from + NEW_TOKEN frame from server. + --sni=<DNSNAME> + Send <DNSNAME> in TLS SNI, overriding the DNS name + specified in <HOST>. + --initial-rtt=<DURATION> + Set an initial RTT. + Default: )" + << util::format_duration(config.initial_rtt) << R"( + --max-window=<SIZE> + Maximum connection-level flow control window size. The + window auto-tuning is enabled if nonzero value is given, + and window size is scaled up to this value. + Default: )" + << util::format_uint_iec(config.max_window) << R"( + --max-stream-window=<SIZE> + Maximum stream-level flow control window size. The + window auto-tuning is enabled if nonzero value is given, + and window size is scaled up to this value. + Default: )" + << util::format_uint_iec(config.max_stream_window) << R"( + --max-udp-payload-size=<SIZE> + Override maximum UDP payload size that client transmits. + --handshake-timeout=<DURATION> + Set the QUIC handshake timeout. + Default: )" + << util::format_duration(config.handshake_timeout) << R"( + --no-pmtud Disables Path MTU Discovery. + --ack-thresh=<N> + The minimum number of the received ACK eliciting packets + that triggers immediate acknowledgement. + Default: )" + << config.ack_thresh << R"( + -h, --help Display this help and exit. + +--- + + The <SIZE> argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The <DURATION> argument is an integer and an optional unit (e.g., 1s + is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms, + us, or ns (hours, minutes, seconds, milliseconds, microseconds, and + nanoseconds respectively). If a unit is omitted, a second is used + as unit. + + The <HEX> argument is an hex string which must start with "0x" + (e.g., 0x00000001).)" + << std::endl; +} +} // namespace + +int main(int argc, char **argv) { + config_set_default(config); + char *data_path = nullptr; + const char *private_key_file = nullptr; + const char *cert_file = nullptr; + + for (;;) { + static int flag = 0; + constexpr static option long_opts[] = { + {"help", no_argument, nullptr, 'h'}, + {"tx-loss", required_argument, nullptr, 't'}, + {"rx-loss", required_argument, nullptr, 'r'}, + {"data", required_argument, nullptr, 'd'}, + {"http-method", required_argument, nullptr, 'm'}, + {"nstreams", required_argument, nullptr, 'n'}, + {"version", required_argument, nullptr, 'v'}, + {"quiet", no_argument, nullptr, 'q'}, + {"show-secret", no_argument, nullptr, 's'}, + {"ciphers", required_argument, &flag, 1}, + {"groups", required_argument, &flag, 2}, + {"timeout", required_argument, &flag, 3}, + {"session-file", required_argument, &flag, 4}, + {"tp-file", required_argument, &flag, 5}, + {"dcid", required_argument, &flag, 6}, + {"change-local-addr", required_argument, &flag, 7}, + {"key-update", required_argument, &flag, 8}, + {"nat-rebinding", no_argument, &flag, 9}, + {"delay-stream", required_argument, &flag, 10}, + {"no-preferred-addr", no_argument, &flag, 11}, + {"key", required_argument, &flag, 12}, + {"cert", required_argument, &flag, 13}, + {"download", required_argument, &flag, 14}, + {"no-quic-dump", no_argument, &flag, 15}, + {"no-http-dump", no_argument, &flag, 16}, + {"qlog-file", required_argument, &flag, 17}, + {"max-data", required_argument, &flag, 18}, + {"max-stream-data-bidi-local", required_argument, &flag, 19}, + {"max-stream-data-bidi-remote", required_argument, &flag, 20}, + {"max-stream-data-uni", required_argument, &flag, 21}, + {"max-streams-bidi", required_argument, &flag, 22}, + {"max-streams-uni", required_argument, &flag, 23}, + {"exit-on-first-stream-close", no_argument, &flag, 24}, + {"disable-early-data", no_argument, &flag, 25}, + {"qlog-dir", required_argument, &flag, 26}, + {"cc", required_argument, &flag, 27}, + {"exit-on-all-streams-close", no_argument, &flag, 28}, + {"token-file", required_argument, &flag, 29}, + {"sni", required_argument, &flag, 30}, + {"initial-rtt", required_argument, &flag, 31}, + {"max-window", required_argument, &flag, 32}, + {"max-stream-window", required_argument, &flag, 33}, + {"max-udp-payload-size", required_argument, &flag, 35}, + {"handshake-timeout", required_argument, &flag, 36}, + {"other-versions", required_argument, &flag, 37}, + {"no-pmtud", no_argument, &flag, 38}, + {"preferred-versions", required_argument, &flag, 39}, + {"ack-thresh", required_argument, &flag, 40}, + {nullptr, 0, nullptr, 0}, + }; + + auto optidx = 0; + auto c = getopt_long(argc, argv, "d:him:n:qr:st:v:", long_opts, &optidx); + if (c == -1) { + break; + } + switch (c) { + case 'd': + // --data + data_path = optarg; + break; + case 'h': + // --help + print_help(); + exit(EXIT_SUCCESS); + case 'm': + // --http-method + config.http_method = optarg; + break; + case 'n': + // --streams + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "streams: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > NGTCP2_MAX_VARINT) { + std::cerr << "streams: must not exceed " << NGTCP2_MAX_VARINT + << std::endl; + exit(EXIT_FAILURE); + } else { + config.nstreams = *n; + } + break; + case 'q': + // --quiet + config.quiet = true; + break; + case 'r': + // --rx-loss + config.rx_loss_prob = strtod(optarg, nullptr); + break; + case 's': + // --show-secret + config.show_secret = true; + break; + case 't': + // --tx-loss + config.tx_loss_prob = strtod(optarg, nullptr); + break; + case 'v': { + // --version + if (optarg == "v1"sv) { + config.version = NGTCP2_PROTO_VER_V1; + break; + } + if (optarg == "v2draft"sv) { + config.version = NGTCP2_PROTO_VER_V2_DRAFT; + break; + } + auto rv = util::parse_version(optarg); + if (!rv) { + std::cerr << "version: invalid version " << std::quoted(optarg) + << std::endl; + exit(EXIT_FAILURE); + } + config.version = *rv; + break; + } + case '?': + print_usage(); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // --ciphers + config.ciphers = optarg; + break; + case 2: + // --groups + config.groups = optarg; + break; + case 3: + // --timeout + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "timeout: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.timeout = *t; + } + break; + case 4: + // --session-file + config.session_file = optarg; + break; + case 5: + // --tp-file + config.tp_file = optarg; + break; + case 6: { + // --dcid + auto dcidlen2 = strlen(optarg); + if (dcidlen2 % 2 || dcidlen2 / 2 < 8 || dcidlen2 / 2 > 18) { + std::cerr << "dcid: wrong length" << std::endl; + exit(EXIT_FAILURE); + } + auto dcid = util::decode_hex(optarg); + ngtcp2_cid_init(&config.dcid, + reinterpret_cast<const uint8_t *>(dcid.c_str()), + dcid.size()); + break; + } + case 7: + // --change-local-addr + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "change-local-addr: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.change_local_addr = *t; + } + break; + case 8: + // --key-update + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "key-update: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.key_update = *t; + } + break; + case 9: + // --nat-rebinding + config.nat_rebinding = true; + break; + case 10: + // --delay-stream + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "delay-stream: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.delay_stream = *t; + } + break; + case 11: + // --no-preferred-addr + config.no_preferred_addr = true; + break; + case 12: + // --key + private_key_file = optarg; + break; + case 13: + // --cert + cert_file = optarg; + break; + case 14: + // --download + config.download = optarg; + break; + case 15: + // --no-quic-dump + config.no_quic_dump = true; + break; + case 16: + // --no-http-dump + config.no_http_dump = true; + break; + case 17: + // --qlog-file + config.qlog_file = optarg; + break; + case 18: + // --max-data + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-data: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_data = *n; + } + break; + case 19: + // --max-stream-data-bidi-local + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-bidi-local: invalid argument" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_bidi_local = *n; + } + break; + case 20: + // --max-stream-data-bidi-remote + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-bidi-remote: invalid argument" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_bidi_remote = *n; + } + break; + case 21: + // --max-stream-data-uni + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-uni: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_uni = *n; + } + break; + case 22: + // --max-streams-bidi + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-streams-bidi: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_streams_bidi = *n; + } + break; + case 23: + // --max-streams-uni + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-streams-uni: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_streams_uni = *n; + } + break; + case 24: + // --exit-on-first-stream-close + config.exit_on_first_stream_close = true; + break; + case 25: + // --disable-early-data + config.disable_early_data = true; + break; + case 26: + // --qlog-dir + config.qlog_dir = optarg; + break; + case 27: + // --cc + if (strcmp("cubic", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_CUBIC; + break; + } + if (strcmp("reno", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_RENO; + break; + } + if (strcmp("bbr", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_BBR; + break; + } + if (strcmp("bbr2", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_BBR2; + break; + } + std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl; + exit(EXIT_FAILURE); + case 28: + // --exit-on-all-streams-close + config.exit_on_all_streams_close = true; + break; + case 29: + // --token-file + config.token_file = optarg; + break; + case 30: + // --sni + config.sni = optarg; + break; + case 31: + // --initial-rtt + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "initial-rtt: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.initial_rtt = *t; + } + break; + case 32: + // --max-window + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-window: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_window = *n; + } + break; + case 33: + // --max-stream-window + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-window: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_window = *n; + } + break; + case 35: + // --max-udp-payload-size + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-udp-payload-size: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 64_k) { + std::cerr << "max-udp-payload-size: must not exceed 65536" + << std::endl; + exit(EXIT_FAILURE); + } else if (*n == 0) { + std::cerr << "max-udp-payload-size: must not be 0" << std::endl; + } else { + config.max_udp_payload_size = *n; + } + break; + case 36: + // --handshake-timeout + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "handshake-timeout: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.handshake_timeout = *t; + } + break; + case 37: { + // --other-versions + if (strlen(optarg) == 0) { + config.other_versions.resize(0); + break; + } + auto l = util::split_str(optarg); + config.other_versions.resize(l.size()); + auto it = std::begin(config.other_versions); + for (const auto &k : l) { + if (k == "v1"sv) { + *it++ = NGTCP2_PROTO_VER_V1; + continue; + } + if (k == "v2draft"sv) { + *it++ = NGTCP2_PROTO_VER_V2_DRAFT; + continue; + } + auto rv = util::parse_version(k); + if (!rv) { + std::cerr << "other-versions: invalid version " << std::quoted(k) + << std::endl; + exit(EXIT_FAILURE); + } + *it++ = *rv; + } + break; + } + case 38: + // --no-pmtud + config.no_pmtud = true; + break; + case 39: { + // --preferred-versions + auto l = util::split_str(optarg); + if (l.size() > max_preferred_versionslen) { + std::cerr << "preferred-versions: too many versions > " + << max_preferred_versionslen << std::endl; + } + config.preferred_versions.resize(l.size()); + auto it = std::begin(config.preferred_versions); + for (const auto &k : l) { + if (k == "v1"sv) { + *it++ = NGTCP2_PROTO_VER_V1; + continue; + } + if (k == "v2draft"sv) { + *it++ = NGTCP2_PROTO_VER_V2_DRAFT; + continue; + } + auto rv = util::parse_version(k); + if (!rv) { + std::cerr << "preferred-versions: invalid version " + << std::quoted(k) << std::endl; + exit(EXIT_FAILURE); + } + if (!ngtcp2_is_supported_version(*rv)) { + std::cerr << "preferred-versions: unsupported version " + << std::quoted(k) << std::endl; + exit(EXIT_FAILURE); + } + *it++ = *rv; + } + break; + } + case 40: + // --ack-thresh + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "ack-thresh: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 100) { + std::cerr << "ack-thresh: must not exceed 100" << std::endl; + exit(EXIT_FAILURE); + } else { + config.ack_thresh = *n; + } + break; + } + break; + default: + break; + }; + } + + if (argc - optind < 2) { + std::cerr << "Too few arguments" << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + if (!config.qlog_file.empty() && !config.qlog_dir.empty()) { + std::cerr << "qlog-file and qlog-dir are mutually exclusive" << std::endl; + exit(EXIT_FAILURE); + } + + if (config.exit_on_first_stream_close && config.exit_on_all_streams_close) { + std::cerr << "exit-on-first-stream-close and exit-on-all-streams-close are " + "mutually exclusive" + << std::endl; + exit(EXIT_FAILURE); + } + + if (data_path) { + auto fd = open(data_path, O_RDONLY); + if (fd == -1) { + std::cerr << "data: Could not open file " << data_path << ": " + << strerror(errno) << std::endl; + exit(EXIT_FAILURE); + } + struct stat st; + if (fstat(fd, &st) != 0) { + std::cerr << "data: Could not stat file " << data_path << ": " + << strerror(errno) << std::endl; + exit(EXIT_FAILURE); + } + config.fd = fd; + config.datalen = st.st_size; + auto addr = mmap(nullptr, config.datalen, PROT_READ, MAP_SHARED, fd, 0); + if (addr == MAP_FAILED) { + std::cerr << "data: Could not mmap file " << data_path << ": " + << strerror(errno) << std::endl; + exit(EXIT_FAILURE); + } + config.data = static_cast<uint8_t *>(addr); + } + + auto addr = argv[optind++]; + auto port = argv[optind++]; + + if (parse_requests(&argv[optind], argc - optind) != 0) { + exit(EXIT_FAILURE); + } + + if (!ngtcp2_is_reserved_version(config.version)) { + if (!config.preferred_versions.empty() && + std::find(std::begin(config.preferred_versions), + std::end(config.preferred_versions), + config.version) == std::end(config.preferred_versions)) { + std::cerr << "preferred-version: must include version " + << "0x" << config.version << std::endl; + exit(EXIT_FAILURE); + } + + if (!config.other_versions.empty() && + std::find(std::begin(config.other_versions), + std::end(config.other_versions), + config.version) == std::end(config.other_versions)) { + std::cerr << "other-versions: must include version " + << "0x" << config.version << std::endl; + exit(EXIT_FAILURE); + } + } + + if (config.nstreams == 0) { + config.nstreams = config.requests.size(); + } + + TLSClientContext tls_ctx; + if (tls_ctx.init(private_key_file, cert_file) != 0) { + exit(EXIT_FAILURE); + } + + auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT); + + auto keylog_filename = getenv("SSLKEYLOGFILE"); + if (keylog_filename) { + keylog_file.open(keylog_filename, std::ios_base::app); + if (keylog_file) { + tls_ctx.enable_keylog(); + } + } + + if (util::generate_secret(config.static_secret.data(), + config.static_secret.size()) != 0) { + std::cerr << "Unable to generate static secret" << std::endl; + exit(EXIT_FAILURE); + } + + auto client_chosen_version = config.version; + + for (;;) { + Client c(EV_DEFAULT, client_chosen_version, config.version); + + if (run(c, addr, port, tls_ctx) != 0) { + exit(EXIT_FAILURE); + } + + if (config.preferred_versions.empty()) { + break; + } + + auto &offered_versions = c.get_offered_versions(); + if (offered_versions.empty()) { + break; + } + + client_chosen_version = ngtcp2_select_version( + config.preferred_versions.data(), config.preferred_versions.size(), + offered_versions.data(), offered_versions.size()); + + if (client_chosen_version == 0) { + std::cerr << "Unable to select a version" << std::endl; + exit(EXIT_FAILURE); + } + + if (!config.quiet) { + std::cerr << "Client selected version " << std::hex << "0x" + << client_chosen_version << std::dec << std::endl; + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/h09client.h b/examples/h09client.h new file mode 100644 index 0000000..702eb17 --- /dev/null +++ b/examples/h09client.h @@ -0,0 +1,196 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef H09CLIENT_H +#define H09CLIENT_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <vector> +#include <deque> +#include <map> +#include <string_view> +#include <memory> +#include <set> + +#include <ngtcp2/ngtcp2.h> +#include <ngtcp2/ngtcp2_crypto.h> + +#include <nghttp3/nghttp3.h> + +#include <ev.h> + +#include "client_base.h" +#include "tls_client_context.h" +#include "tls_client_session.h" +#include "network.h" +#include "shared.h" +#include "template.h" + +using namespace ngtcp2; + +struct Stream { + Stream(const Request &req, int64_t stream_id); + ~Stream(); + + int open_file(const std::string_view &path); + + Request req; + int64_t stream_id; + int fd; + std::string rawreqbuf; + nghttp3_buf reqbuf; +}; + +struct StreamIDLess { + constexpr bool operator()(const Stream *lhs, const Stream *rhs) const { + return lhs->stream_id < rhs->stream_id; + } +}; + +class Client; + +struct Endpoint { + Address addr; + ev_io rev; + Client *client; + int fd; +}; + +class Client : public ClientBase { +public: + Client(struct ev_loop *loop, uint32_t client_chosen_version, + uint32_t original_version); + ~Client(); + + int init(int fd, const Address &local_addr, const Address &remote_addr, + const char *addr, const char *port, TLSClientContext &tls_ctx); + void disconnect(); + + int on_read(const Endpoint &ep); + int on_write(); + int write_streams(); + int feed_data(const Endpoint &ep, const sockaddr *sa, socklen_t salen, + const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen); + int handle_expiry(); + void update_timer(); + int handshake_completed(); + int handshake_confirmed(); + void recv_version_negotiation(const uint32_t *sv, size_t nsv); + + int send_packet(const Endpoint &ep, const ngtcp2_addr &remote_addr, + unsigned int ecn, const uint8_t *data, size_t datalen); + int on_stream_close(int64_t stream_id, uint64_t app_error_code); + int on_extend_max_streams(); + int handle_error(); + int make_stream_early(); + int change_local_addr(); + void start_change_local_addr_timer(); + int update_key(uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen); + int initiate_key_update(); + void start_key_update_timer(); + void start_delay_stream_timer(); + + int select_preferred_address(Address &selected_addr, + const ngtcp2_preferred_addr *paddr); + + std::optional<Endpoint *> endpoint_for(const Address &remote_addr); + + void set_remote_addr(const ngtcp2_addr &remote_addr); + + int submit_http_request(Stream *stream); + int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data, + size_t datalen); + int acked_stream_data_offset(int64_t stream_id, uint64_t offset, + uint64_t datalen); + int extend_max_stream_data(int64_t stream_id, uint64_t max_data); + + void write_qlog(const void *data, size_t datalen); + + void on_send_blocked(const Endpoint &ep, const ngtcp2_addr &remote_addr, + unsigned int ecn, size_t datalen); + void start_wev_endpoint(const Endpoint &ep); + int send_blocked_packet(); + + const std::vector<uint32_t> &get_offered_versions() const; + + void early_data_rejected(); + +private: + std::vector<Endpoint> endpoints_; + Address remote_addr_; + ev_io wev_; + ev_timer timer_; + ev_timer change_local_addr_timer_; + ev_timer key_update_timer_; + ev_timer delay_stream_timer_; + ev_signal sigintev_; + struct ev_loop *loop_; + std::map<int64_t, std::unique_ptr<Stream>> streams_; + std::set<Stream *, StreamIDLess> sendq_; + std::vector<uint32_t> offered_versions_; + // addr_ is the server host address. + const char *addr_; + // port_ is the server port. + const char *port_; + // nstreams_done_ is the number of streams opened. + size_t nstreams_done_; + // nstreams_closed_ is the number of streams get closed. + size_t nstreams_closed_; + // nkey_update_ is the number of key update occurred. + size_t nkey_update_; + uint32_t client_chosen_version_; + uint32_t original_version_; + // early_data_ is true if client attempts to do 0RTT data transfer. + bool early_data_; + // should_exit_ is true if client should exit rather than waiting + // for timeout. + bool should_exit_; + // should_exit_on_handshake_confirmed_ is true if client should exit + // when handshake confirmed. + bool should_exit_on_handshake_confirmed_; + // handshake_confirmed_ gets true after handshake has been + // confirmed. + bool handshake_confirmed_; + + struct { + bool send_blocked; + // blocked field is effective only when send_blocked is true. + struct { + const Endpoint *endpoint; + Address remote_addr; + unsigned int ecn; + size_t datalen; + } blocked; + std::array<uint8_t, 64_k> data; + } tx_; +}; + +#endif // CLIENT_H diff --git a/examples/h09server.cc b/examples/h09server.cc new file mode 100644 index 0000000..4af67da --- /dev/null +++ b/examples/h09server.cc @@ -0,0 +1,3034 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include <chrono> +#include <cstdlib> +#include <cassert> +#include <cstring> +#include <iostream> +#include <algorithm> +#include <memory> +#include <fstream> +#include <iomanip> + +#include <unistd.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <netinet/udp.h> +#include <net/if.h> + +#include <http-parser/http_parser.h> + +#include "h09server.h" +#include "network.h" +#include "debug.h" +#include "util.h" +#include "shared.h" +#include "http.h" +#include "template.h" + +using namespace ngtcp2; +using namespace std::literals; + +namespace { +constexpr size_t NGTCP2_SV_SCIDLEN = 18; +} // namespace + +namespace { +constexpr size_t max_preferred_versionslen = 4; +} // namespace + +namespace { +auto randgen = util::make_mt19937(); +} // namespace + +Config config{}; + +Stream::Stream(int64_t stream_id, Handler *handler) + : stream_id(stream_id), handler(handler), eos(false) { + nghttp3_buf_init(&respbuf); + htp.data = this; + http_parser_init(&htp, HTTP_REQUEST); +} + +namespace { +constexpr auto NGTCP2_SERVER = "ngtcp2 server"sv; +} // namespace + +namespace { +std::string make_status_body(unsigned int status_code) { + auto status_string = util::format_uint(status_code); + auto reason_phrase = http::get_reason_phrase(status_code); + + std::string body; + body = "<html><head><title>"; + body += status_string; + body += ' '; + body += reason_phrase; + body += "</title></head><body><h1>"; + body += status_string; + body += ' '; + body += reason_phrase; + body += "</h1><hr><address>"; + body += NGTCP2_SERVER; + body += " at port "; + body += util::format_uint(config.port); + body += "</address>"; + body += "</body></html>"; + return body; +} +} // namespace + +struct Request { + std::string path; +}; + +namespace { +Request request_path(const std::string_view &uri) { + http_parser_url u; + Request req; + + http_parser_url_init(&u); + + if (auto rv = http_parser_parse_url(uri.data(), uri.size(), + /* is_connect = */ 0, &u); + rv != 0) { + return req; + } + + if (u.field_set & (1 << UF_PATH)) { + req.path = std::string(uri.data() + u.field_data[UF_PATH].off, + u.field_data[UF_PATH].len); + if (req.path.find('%') != std::string::npos) { + req.path = util::percent_decode(std::begin(req.path), std::end(req.path)); + } + if (!req.path.empty() && req.path.back() == '/') { + req.path += "index.html"; + } + } else { + req.path = "/index.html"; + } + + req.path = util::normalize_path(req.path); + if (req.path == "/") { + req.path = "/index.html"; + } + + return req; +} +} // namespace + +enum FileEntryFlag { + FILE_ENTRY_TYPE_DIR = 0x1, +}; + +struct FileEntry { + uint64_t len; + void *map; + int fd; + uint8_t flags; +}; + +namespace { +std::unordered_map<std::string, FileEntry> file_cache; +} // namespace + +std::pair<FileEntry, int> Stream::open_file(const std::string &path) { + auto it = file_cache.find(path); + if (it != std::end(file_cache)) { + return {(*it).second, 0}; + } + + auto fd = open(path.c_str(), O_RDONLY); + if (fd == -1) { + return {{}, -1}; + } + + struct stat st {}; + if (fstat(fd, &st) != 0) { + close(fd); + return {{}, -1}; + } + + FileEntry fe{}; + if (st.st_mode & S_IFDIR) { + fe.flags |= FILE_ENTRY_TYPE_DIR; + fe.fd = -1; + close(fd); + } else { + fe.fd = fd; + fe.len = st.st_size; + fe.map = mmap(nullptr, fe.len, PROT_READ, MAP_SHARED, fd, 0); + if (fe.map == MAP_FAILED) { + std::cerr << "mmap: " << strerror(errno) << std::endl; + close(fd); + return {{}, -1}; + } + } + + file_cache.emplace(path, fe); + + return {std::move(fe), 0}; +} + +void Stream::map_file(const FileEntry &fe) { + respbuf.begin = respbuf.pos = static_cast<uint8_t *>(fe.map); + respbuf.end = respbuf.last = respbuf.begin + fe.len; +} + +int Stream::send_status_response(unsigned int status_code) { + status_resp_body = make_status_body(status_code); + + respbuf.begin = respbuf.pos = + reinterpret_cast<uint8_t *>(status_resp_body.data()); + respbuf.end = respbuf.last = respbuf.begin + status_resp_body.size(); + + handler->add_sendq(this); + handler->shutdown_read(stream_id, 0); + + return 0; +} + +int Stream::start_response() { + if (uri.empty()) { + return send_status_response(400); + } + + auto req = request_path(uri); + if (req.path.empty()) { + return send_status_response(400); + } + + auto path = config.htdocs + req.path; + auto [fe, rv] = open_file(path); + if (rv != 0) { + send_status_response(404); + return 0; + } + + if (fe.flags & FILE_ENTRY_TYPE_DIR) { + send_status_response(308); + return 0; + } + + map_file(fe); + + if (!config.quiet) { + std::array<nghttp3_nv, 1> nva{ + util::make_nv_nn(":status", "200"), + }; + + debug::print_http_response_headers(stream_id, nva.data(), nva.size()); + } + + handler->add_sendq(this); + + return 0; +} + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto h = static_cast<Handler *>(w->data); + auto s = h->server(); + + switch (h->on_write()) { + case 0: + case NETWORK_ERR_CLOSE_WAIT: + return; + default: + s->remove(h); + } +} +} // namespace + +namespace { +void close_waitcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto h = static_cast<Handler *>(w->data); + auto s = h->server(); + auto conn = h->conn(); + + if (ngtcp2_conn_is_in_closing_period(conn)) { + if (!config.quiet) { + std::cerr << "Closing Period is over" << std::endl; + } + + s->remove(h); + return; + } + if (ngtcp2_conn_is_in_draining_period(conn)) { + if (!config.quiet) { + std::cerr << "Draining Period is over" << std::endl; + } + + s->remove(h); + return; + } + + assert(0); +} +} // namespace + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + int rv; + + auto h = static_cast<Handler *>(w->data); + auto s = h->server(); + + if (!config.quiet) { + std::cerr << "Timer expired" << std::endl; + } + + rv = h->handle_expiry(); + if (rv != 0) { + goto fail; + } + + rv = h->on_write(); + if (rv != 0) { + goto fail; + } + + return; + +fail: + switch (rv) { + case NETWORK_ERR_CLOSE_WAIT: + ev_timer_stop(loop, w); + return; + default: + s->remove(h); + return; + } +} +} // namespace + +Handler::Handler(struct ev_loop *loop, Server *server) + : loop_(loop), + server_(server), + qlog_(nullptr), + scid_{}, + nkey_update_(0), + no_gso_{ +#ifdef UDP_SEGMENT + false +#else // !UDP_SEGMENT + true +#endif // !UDP_SEGMENT + }, + tx_{ + .data = std::unique_ptr<uint8_t[]>(new uint8_t[64_k]), + } { + ev_io_init(&wev_, writecb, 0, EV_WRITE); + wev_.data = this; + ev_timer_init(&timer_, timeoutcb, 0., 0.); + timer_.data = this; +} + +Handler::~Handler() { + if (!config.quiet) { + std::cerr << scid_ << " Closing QUIC connection " << std::endl; + } + + ev_timer_stop(loop_, &timer_); + ev_io_stop(loop_, &wev_); + + if (qlog_) { + fclose(qlog_); + } +} + +namespace { +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + auto h = static_cast<Handler *>(user_data); + + if (!config.quiet) { + debug::handshake_completed(conn, user_data); + } + + if (h->handshake_completed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Handler::handshake_completed() { + if (!config.quiet) { + std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name() + << std::endl; + std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn() + << std::endl; + } + + std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN> token; + + auto path = ngtcp2_conn_get_path(conn_); + auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + auto tokenlen = ngtcp2_crypto_generate_regular_token( + token.data(), config.static_secret.data(), config.static_secret.size(), + path->remote.addr, path->remote.addrlen, t); + if (tokenlen < 0) { + if (!config.quiet) { + std::cerr << "Unable to generate token" << std::endl; + } + return 0; + } + + if (auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen); + rv != 0) { + if (!config.quiet) { + std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv) + << std::endl; + } + return -1; + } + + return 0; +} + +namespace { +int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) { + if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (!config.quiet && config.show_secret) { + debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN); + } + + return 0; +} +} // namespace + +namespace { +int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data) { + if (!config.quiet && !config.no_quic_dump) { + debug::print_crypto_data(crypto_level, data, datalen); + } + + return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data, + datalen, user_data); +} +} // namespace + +namespace { +int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + + if (h->recv_stream_data(flags, stream_id, data, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t offset, uint64_t datalen, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->acked_stream_data_offset(stream_id, offset, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::acked_stream_data_offset(int64_t stream_id, uint64_t offset, + uint64_t datalen) { + auto it = streams_.find(stream_id); + assert(it != std::end(streams_)); + auto &stream = (*it).second; + (void)stream; + + assert(static_cast<uint64_t>(stream->respbuf.end - stream->respbuf.begin) >= + offset + datalen); + + return 0; +} + +namespace { +int stream_open(ngtcp2_conn *conn, int64_t stream_id, void *user_data) { + auto h = static_cast<Handler *>(user_data); + h->on_stream_open(stream_id); + return 0; +} +} // namespace + +void Handler::on_stream_open(int64_t stream_id) { + if (!ngtcp2_is_bidi_stream(stream_id)) { + return; + } + auto it = streams_.find(stream_id); + (void)it; + assert(it == std::end(streams_)); + streams_.emplace(stream_id, std::make_unique<Stream>(stream_id, this)); +} + +namespace { +int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->on_stream_close(stream_id, app_error_code) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +namespace { +void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { + auto dis = std::uniform_int_distribution<uint8_t>(0, 255); + std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); }); +} +} // namespace + +namespace { +int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, + size_t cidlen, void *user_data) { + if (util::generate_secure_random(cid->data, cidlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + cid->datalen = cidlen; + if (ngtcp2_crypto_generate_stateless_reset_token( + token, config.static_secret.data(), config.static_secret.size(), + cid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + auto h = static_cast<Handler *>(user_data); + h->server()->associate_cid(cid, h); + + return 0; +} +} // namespace + +namespace { +int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid, + void *user_data) { + auto h = static_cast<Handler *>(user_data); + h->server()->dissociate_cid(cid); + return 0; +} +} // namespace + +namespace { +int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen, + void *user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx, + tx_iv, current_rx_secret, current_tx_secret, + secretlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +namespace { +int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path, + ngtcp2_path_validation_result res, void *user_data) { + if (!config.quiet) { + debug::path_validation(path, res); + } + return 0; +} +} // namespace + +namespace { +int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->extend_max_stream_data(stream_id, max_data) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::extend_max_stream_data(int64_t stream_id, uint64_t max_data) { + auto it = streams_.find(stream_id); + assert(it != std::end(streams_)); + auto &stream = (*it).second; + + if (nghttp3_buf_len(&stream->respbuf)) { + sendq_.emplace(stream.get()); + } + + return 0; +} + +namespace { +void write_qlog(void *user_data, uint32_t flags, const void *data, + size_t datalen) { + auto h = static_cast<Handler *>(user_data); + h->write_qlog(data, datalen); +} +} // namespace + +void Handler::write_qlog(const void *data, size_t datalen) { + assert(qlog_); + fwrite(data, 1, datalen, qlog_); +} + +int Handler::init(const Endpoint &ep, const Address &local_addr, + const sockaddr *sa, socklen_t salen, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_cid *ocid, + const uint8_t *token, size_t tokenlen, uint32_t version, + TLSServerContext &tls_ctx) { + auto callbacks = ngtcp2_callbacks{ + nullptr, // client_initial + ngtcp2_crypto_recv_client_initial_cb, + ::recv_crypto_data, + ::handshake_completed, + nullptr, // recv_version_negotiation + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + do_hp_mask, + ::recv_stream_data, + ::acked_stream_data_offset, + stream_open, + stream_close, + nullptr, // recv_stateless_reset + nullptr, // recv_retry + nullptr, // extend_max_streams_bidi + nullptr, // extend_max_streams_uni + rand, + get_new_connection_id, + remove_connection_id, + ::update_key, + path_validation, + nullptr, // select_preferred_addr + nullptr, // stream_reset + nullptr, // extend_max_remote_streams_bidi + nullptr, // extend_max_remote_streams_uni + ::extend_max_stream_data, + nullptr, // dcid_status + nullptr, // handshake_confirmed + nullptr, // recv_new_token + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + nullptr, // recv_datagram + nullptr, // ack_datagram + nullptr, // lost_datagram + ngtcp2_crypto_get_path_challenge_data_cb, + nullptr, // stream_stop_sending + ngtcp2_crypto_version_negotiation_cb, + nullptr, // recv_rx_key + nullptr, // recv_tx_key + }; + + scid_.datalen = NGTCP2_SV_SCIDLEN; + if (util::generate_secure_random(scid_.data, scid_.datalen) != 0) { + std::cerr << "Could not generate connection ID" << std::endl; + return -1; + } + + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + settings.log_printf = config.quiet ? nullptr : debug::log_printf; + settings.initial_ts = util::timestamp(loop_); + settings.token = ngtcp2_vec{const_cast<uint8_t *>(token), tokenlen}; + settings.cc_algo = config.cc_algo; + settings.initial_rtt = config.initial_rtt; + settings.max_window = config.max_window; + settings.max_stream_window = config.max_stream_window; + settings.handshake_timeout = config.handshake_timeout; + settings.no_pmtud = config.no_pmtud; + settings.ack_thresh = config.ack_thresh; + if (config.max_udp_payload_size) { + settings.max_tx_udp_payload_size = config.max_udp_payload_size; + settings.no_tx_udp_payload_size_shaping = 1; + } + if (!config.qlog_dir.empty()) { + auto path = std::string{config.qlog_dir}; + path += '/'; + path += util::format_hex(scid_.data, scid_.datalen); + path += ".sqlog"; + qlog_ = fopen(path.c_str(), "w"); + if (qlog_ == nullptr) { + std::cerr << "Could not open qlog file " << std::quoted(path) << ": " + << strerror(errno) << std::endl; + return -1; + } + settings.qlog.write = ::write_qlog; + settings.qlog.odcid = *scid; + } + if (!config.preferred_versions.empty()) { + settings.preferred_versions = config.preferred_versions.data(); + settings.preferred_versionslen = config.preferred_versions.size(); + } + if (!config.other_versions.empty()) { + settings.other_versions = config.other_versions.data(); + settings.other_versionslen = config.other_versions.size(); + } + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; + params.initial_max_stream_data_bidi_remote = + config.max_stream_data_bidi_remote; + params.initial_max_stream_data_uni = config.max_stream_data_uni; + params.initial_max_data = config.max_data; + params.initial_max_streams_bidi = config.max_streams_bidi; + params.initial_max_streams_uni = 0; + params.max_idle_timeout = config.timeout; + params.stateless_reset_token_present = 1; + params.active_connection_id_limit = 7; + + if (ocid) { + params.original_dcid = *ocid; + params.retry_scid = *scid; + params.retry_scid_present = 1; + } else { + params.original_dcid = *scid; + } + + if (util::generate_secure_random(params.stateless_reset_token, + sizeof(params.stateless_reset_token)) != 0) { + std::cerr << "Could not generate stateless reset token" << std::endl; + return -1; + } + + if (config.preferred_ipv4_addr.len || config.preferred_ipv6_addr.len) { + params.preferred_address_present = 1; + + if (config.preferred_ipv4_addr.len) { + params.preferred_address.ipv4 = config.preferred_ipv4_addr.su.in; + params.preferred_address.ipv4_present = 1; + } + + if (config.preferred_ipv6_addr.len) { + params.preferred_address.ipv6 = config.preferred_ipv6_addr.su.in6; + params.preferred_address.ipv6_present = 1; + } + + auto &token = params.preferred_address.stateless_reset_token; + if (util::generate_secure_random(token, sizeof(token)) != 0) { + std::cerr << "Could not generate preferred address stateless reset token" + << std::endl; + return -1; + } + + params.preferred_address.cid.datalen = NGTCP2_SV_SCIDLEN; + if (util::generate_secure_random(params.preferred_address.cid.data, + params.preferred_address.cid.datalen) != + 0) { + std::cerr << "Could not generate preferred address connection ID" + << std::endl; + return -1; + } + } + + auto path = ngtcp2_path{ + { + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }, + { + const_cast<sockaddr *>(sa), + salen, + }, + const_cast<Endpoint *>(&ep), + }; + if (auto rv = + ngtcp2_conn_server_new(&conn_, dcid, &scid_, &path, version, + &callbacks, &settings, ¶ms, nullptr, this); + rv != 0) { + std::cerr << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv) << std::endl; + return -1; + } + + if (tls_session_.init(tls_ctx, this) != 0) { + return -1; + } + + tls_session_.enable_keylog(); + + ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle()); + + ev_io_set(&wev_, ep.fd, EV_WRITE); + + return 0; +} + +int Handler::feed_data(const Endpoint &ep, const Address &local_addr, + const sockaddr *sa, socklen_t salen, + const ngtcp2_pkt_info *pi, uint8_t *data, + size_t datalen) { + auto path = ngtcp2_path{ + { + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }, + { + const_cast<sockaddr *>(sa), + salen, + }, + const_cast<Endpoint *>(&ep), + }; + + if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen, + util::timestamp(loop_)); + rv != 0) { + std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; + switch (rv) { + case NGTCP2_ERR_DRAINING: + start_draining_period(); + return NETWORK_ERR_CLOSE_WAIT; + case NGTCP2_ERR_RETRY: + return NETWORK_ERR_RETRY; + case NGTCP2_ERR_DROP_CONN: + return NETWORK_ERR_DROP_CONN; + case NGTCP2_ERR_CRYPTO: + if (!last_error_.error_code) { + ngtcp2_connection_close_error_set_transport_error_tls_alert( + &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0); + } + break; + default: + if (!last_error_.error_code) { + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, rv, nullptr, 0); + } + } + return handle_error(); + } + + return 0; +} + +int Handler::on_read(const Endpoint &ep, const Address &local_addr, + const sockaddr *sa, socklen_t salen, + const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen) { + if (auto rv = feed_data(ep, local_addr, sa, salen, pi, data, datalen); + rv != 0) { + return rv; + } + + update_timer(); + + return 0; +} + +int Handler::handle_expiry() { + auto now = util::timestamp(loop_); + if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) { + std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_transport_error_liberr(&last_error_, rv, + nullptr, 0); + return handle_error(); + } + + return 0; +} + +int Handler::on_write() { + if (ngtcp2_conn_is_in_closing_period(conn_) || + ngtcp2_conn_is_in_draining_period(conn_)) { + return 0; + } + + if (tx_.send_blocked) { + if (auto rv = send_blocked_packet(); rv != 0) { + return rv; + } + + if (tx_.send_blocked) { + return 0; + } + } + + if (auto rv = write_streams(); rv != 0) { + return rv; + } + + update_timer(); + + return 0; +} + +int Handler::write_streams() { + ngtcp2_vec vec; + ngtcp2_path_storage ps, prev_ps; + uint32_t prev_ecn = 0; + size_t pktcnt = 0; + auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_); + auto path_max_udp_payload_size = + ngtcp2_conn_get_path_max_tx_udp_payload_size(conn_); + auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size; + uint8_t *bufpos = tx_.data.get(); + ngtcp2_pkt_info pi; + size_t gso_size = 0; + auto ts = util::timestamp(loop_); + + ngtcp2_path_storage_zero(&ps); + ngtcp2_path_storage_zero(&prev_ps); + + max_pktcnt = std::min(max_pktcnt, static_cast<size_t>(config.max_gso_dgrams)); + + for (;;) { + int64_t stream_id = -1; + size_t vcnt = 0; + uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + Stream *stream = nullptr; + + if (!sendq_.empty() && ngtcp2_conn_get_max_data_left(conn_)) { + stream = *std::begin(sendq_); + + stream_id = stream->stream_id; + vec.base = stream->respbuf.pos; + vec.len = nghttp3_buf_len(&stream->respbuf); + vcnt = 1; + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + + ngtcp2_ssize ndatalen; + + auto nwrite = ngtcp2_conn_writev_stream(conn_, &ps.path, &pi, bufpos, + max_udp_payload_size, &ndatalen, + flags, stream_id, &vec, vcnt, ts); + if (nwrite < 0) { + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + case NGTCP2_ERR_STREAM_SHUT_WR: + assert(ndatalen == -1); + sendq_.erase(std::begin(sendq_)); + continue; + case NGTCP2_ERR_WRITE_MORE: + assert(ndatalen >= 0); + stream->respbuf.pos += ndatalen; + if (nghttp3_buf_len(&stream->respbuf) == 0) { + sendq_.erase(std::begin(sendq_)); + } + continue; + } + + assert(ndatalen == -1); + + std::cerr << "ngtcp2_conn_writev_stream: " << ngtcp2_strerror(nwrite) + << std::endl; + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, nwrite, nullptr, 0); + return handle_error(); + } else if (ndatalen >= 0) { + stream->respbuf.pos += ndatalen; + if (nghttp3_buf_len(&stream->respbuf) == 0) { + sendq_.erase(std::begin(sendq_)); + } + } + + if (nwrite == 0) { + if (bufpos - tx_.data.get()) { + auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data; + + if (auto [nsent, rv] = server_->send_packet( + ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn, + data, datalen, gso_size); + rv != NETWORK_ERR_OK) { + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn, + data + nsent, datalen - nsent, gso_size); + + start_wev_endpoint(ep); + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + return 0; + } + } + + ev_io_stop(loop_, &wev_); + + // We are congestion limited. + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + return 0; + } + + bufpos += nwrite; + + if (pktcnt == 0) { + ngtcp2_path_copy(&prev_ps.path, &ps.path); + prev_ecn = pi.ecn; + gso_size = nwrite; + } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) || prev_ecn != pi.ecn || + static_cast<size_t>(nwrite) > gso_size || + (gso_size > path_max_udp_payload_size && + static_cast<size_t>(nwrite) != gso_size)) { + auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data - nwrite; + + if (auto [nsent, rv] = server_->send_packet( + ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn, + data, datalen, gso_size); + rv != 0) { + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn, + data + nsent, datalen - nsent, gso_size); + + on_send_blocked(*static_cast<Endpoint *>(ps.path.user_data), + ps.path.local, ps.path.remote, pi.ecn, bufpos - nwrite, + nwrite, 0); + + start_wev_endpoint(ep); + } else { + auto &ep = *static_cast<Endpoint *>(ps.path.user_data); + auto data = bufpos - nwrite; + + if (auto [nsent, rv] = + server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote, + pi.ecn, data, nwrite, nwrite); + rv != 0) { + assert(nsent == 0); + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data, + nwrite, 0); + } + + start_wev_endpoint(ep); + } + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + return 0; + } + + if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < gso_size) { + auto &ep = *static_cast<Endpoint *>(ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data; + + if (auto [nsent, rv] = + server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote, + pi.ecn, data, datalen, gso_size); + rv != 0) { + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data + nsent, + datalen - nsent, gso_size); + } + + start_wev_endpoint(ep); + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + return 0; + } + } +} + +void Handler::on_send_blocked(Endpoint &ep, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen, + size_t gso_size) { + assert(tx_.num_blocked || !tx_.send_blocked); + assert(tx_.num_blocked < 2); + + tx_.send_blocked = true; + + auto &p = tx_.blocked[tx_.num_blocked++]; + + memcpy(&p.local_addr.su, local_addr.addr, local_addr.addrlen); + memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen); + + p.local_addr.len = local_addr.addrlen; + p.remote_addr.len = remote_addr.addrlen; + p.endpoint = &ep; + p.ecn = ecn; + p.data = data; + p.datalen = datalen; + p.gso_size = gso_size; +} + +void Handler::start_wev_endpoint(const Endpoint &ep) { + // We do not close ep.fd, so we can expect that each Endpoint has + // unique fd. + if (ep.fd != wev_.fd) { + if (ev_is_active(&wev_)) { + ev_io_stop(loop_, &wev_); + } + + ev_io_set(&wev_, ep.fd, EV_WRITE); + } + + ev_io_start(loop_, &wev_); +} + +int Handler::send_blocked_packet() { + assert(tx_.send_blocked); + + for (; tx_.num_blocked_sent < tx_.num_blocked; ++tx_.num_blocked_sent) { + auto &p = tx_.blocked[tx_.num_blocked_sent]; + + ngtcp2_addr local_addr{ + .addr = &p.local_addr.su.sa, + .addrlen = p.local_addr.len, + }; + ngtcp2_addr remote_addr{ + .addr = &p.remote_addr.su.sa, + .addrlen = p.remote_addr.len, + }; + + auto [nsent, rv] = + server_->send_packet(*p.endpoint, no_gso_, local_addr, remote_addr, + p.ecn, p.data, p.datalen, p.gso_size); + if (rv != 0) { + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + p.data += nsent; + p.datalen -= nsent; + + start_wev_endpoint(*p.endpoint); + + return 0; + } + } + + tx_.send_blocked = false; + tx_.num_blocked = 0; + tx_.num_blocked_sent = 0; + + return 0; +} + +void Handler::signal_write() { ev_io_start(loop_, &wev_); } + +void Handler::start_draining_period() { + ev_io_stop(loop_, &wev_); + + ev_set_cb(&timer_, close_waitcb); + timer_.repeat = + static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3; + ev_timer_again(loop_, &timer_); + + if (!config.quiet) { + std::cerr << "Draining period has started (" << timer_.repeat << " seconds)" + << std::endl; + } +} + +int Handler::start_closing_period() { + if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_) || + ngtcp2_conn_is_in_draining_period(conn_)) { + return 0; + } + + ev_io_stop(loop_, &wev_); + + ev_set_cb(&timer_, close_waitcb); + timer_.repeat = + static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3; + ev_timer_again(loop_, &timer_); + + if (!config.quiet) { + std::cerr << "Closing period has started (" << timer_.repeat << " seconds)" + << std::endl; + } + + conn_closebuf_ = std::make_unique<Buffer>(NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + ngtcp2_path_storage ps; + + ngtcp2_path_storage_zero(&ps); + + ngtcp2_pkt_info pi; + auto n = ngtcp2_conn_write_connection_close( + conn_, &ps.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(), + &last_error_, util::timestamp(loop_)); + if (n < 0) { + std::cerr << "ngtcp2_conn_write_connection_close: " << ngtcp2_strerror(n) + << std::endl; + return -1; + } + + if (n == 0) { + return 0; + } + + conn_closebuf_->push(n); + + return 0; +} + +int Handler::handle_error() { + if (last_error_.type == + NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE) { + return -1; + } + + if (start_closing_period() != 0) { + return -1; + } + + if (ngtcp2_conn_is_in_draining_period(conn_)) { + return NETWORK_ERR_CLOSE_WAIT; + } + + if (auto rv = send_conn_close(); rv != NETWORK_ERR_OK) { + return rv; + } + + return NETWORK_ERR_CLOSE_WAIT; +} + +int Handler::send_conn_close() { + if (!config.quiet) { + std::cerr << "Closing Period: TX CONNECTION_CLOSE" << std::endl; + } + + assert(conn_closebuf_ && conn_closebuf_->size()); + assert(conn_); + assert(!ngtcp2_conn_is_in_draining_period(conn_)); + + auto path = ngtcp2_conn_get_path(conn_); + + return server_->send_packet( + *static_cast<Endpoint *>(path->user_data), path->local, path->remote, + /* ecn = */ 0, conn_closebuf_->rpos(), conn_closebuf_->size()); +} + +void Handler::update_timer() { + auto expiry = ngtcp2_conn_get_expiry(conn_); + auto now = util::timestamp(loop_); + + if (expiry <= now) { + if (!config.quiet) { + auto t = static_cast<ev_tstamp>(now - expiry) / NGTCP2_SECONDS; + std::cerr << "Timer has already expired: " << t << "s" << std::endl; + } + + ev_feed_event(loop_, &timer_, EV_TIMER); + + return; + } + + auto t = static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS; + if (!config.quiet) { + std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat + << std::endl; + } + timer_.repeat = t; + ev_timer_again(loop_, &timer_); +} + +namespace { +int on_msg_begin(http_parser *htp) { + auto s = static_cast<Stream *>(htp->data); + if (s->eos) { + return -1; + } + return 0; +} +} // namespace + +namespace { +int on_url_cb(http_parser *htp, const char *data, size_t datalen) { + auto s = static_cast<Stream *>(htp->data); + s->uri.append(data, datalen); + return 0; +} +} // namespace + +namespace { +int on_msg_complete(http_parser *htp) { + auto s = static_cast<Stream *>(htp->data); + s->eos = true; + if (s->start_response() != 0) { + return -1; + } + return 0; +} +} // namespace + +auto htp_settings = http_parser_settings{ + on_msg_begin, // on_message_begin + on_url_cb, // on_url + nullptr, // on_status + nullptr, // on_header_field + nullptr, // on_header_value + nullptr, // on_headers_complete + nullptr, // on_body + on_msg_complete, // on_message_complete + nullptr, // on_chunk_header, + nullptr, // on_chunk_complete +}; + +int Handler::recv_stream_data(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + if (!config.quiet && !config.no_quic_dump) { + debug::print_stream_data(stream_id, data, datalen); + } + + auto it = streams_.find(stream_id); + assert(it != std::end(streams_)); + auto &stream = (*it).second; + + if (!stream->eos) { + auto nread = + http_parser_execute(&stream->htp, &htp_settings, + reinterpret_cast<const char *>(data), datalen); + if (nread != datalen) { + if (auto rv = ngtcp2_conn_shutdown_stream(conn_, stream_id, + /* app error code */ 1); + rv != 0) { + std::cerr << "ngtcp2_conn_shutdown_stream: " << ngtcp2_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, NGTCP2_ERR_INTERNAL, nullptr, 0); + return -1; + } + } + } + + ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, datalen); + ngtcp2_conn_extend_max_offset(conn_, datalen); + + return 0; +} + +int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen) { + auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_); + auto aead = &crypto_ctx->aead; + auto keylen = ngtcp2_crypto_aead_keylen(aead); + auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + + ++nkey_update_; + + std::array<uint8_t, 64> rx_key, tx_key; + + if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx, + rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(), + tx_iv, current_rx_secret, current_tx_secret, + secretlen) != 0) { + return -1; + } + + if (!config.quiet && config.show_secret) { + std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl; + debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv, + ivlen); + std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl; + debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv, + ivlen); + } + + return 0; +} + +Server *Handler::server() const { return server_; } + +int Handler::on_stream_close(int64_t stream_id, uint64_t app_error_code) { + if (!config.quiet) { + std::cerr << "QUIC stream " << stream_id << " closed" << std::endl; + } + + auto it = streams_.find(stream_id); + assert(it != std::end(streams_)); + auto &stream = (*it).second; + + sendq_.erase(stream.get()); + + if (!config.quiet) { + std::cerr << "HTTP stream " << stream_id << " closed with error code " + << app_error_code << std::endl; + } + + streams_.erase(it); + + if (ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(conn_, stream_id)); + ngtcp2_conn_extend_max_streams_bidi(conn_, 1); + } + + return 0; +} + +void Handler::shutdown_read(int64_t stream_id, int app_error_code) { + ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code); +} + +void Handler::add_sendq(Stream *stream) { sendq_.emplace(stream); } + +namespace { +void sreadcb(struct ev_loop *loop, ev_io *w, int revents) { + auto ep = static_cast<Endpoint *>(w->data); + + ep->server->on_read(*ep); +} +} // namespace + +namespace { +void siginthandler(struct ev_loop *loop, ev_signal *watcher, int revents) { + ev_break(loop, EVBREAK_ALL); +} +} // namespace + +Server::Server(struct ev_loop *loop, TLSServerContext &tls_ctx) + : loop_(loop), tls_ctx_(tls_ctx) { + ev_signal_init(&sigintev_, siginthandler, SIGINT); +} + +Server::~Server() { + disconnect(); + close(); +} + +void Server::disconnect() { + config.tx_loss_prob = 0; + + for (auto &ep : endpoints_) { + ev_io_stop(loop_, &ep.rev); + } + + ev_signal_stop(loop_, &sigintev_); + + while (!handlers_.empty()) { + auto it = std::begin(handlers_); + auto &h = (*it).second; + + h->handle_error(); + + remove(h); + } +} + +void Server::close() { + for (auto &ep : endpoints_) { + ::close(ep.fd); + } + + endpoints_.clear(); +} + +namespace { +int create_sock(Address &local_addr, const char *addr, const char *port, + int family) { + addrinfo hints{}; + addrinfo *res, *rp; + int val = 1; + + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + if (strcmp(addr, "*") == 0) { + addr = nullptr; + } + + if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) { + std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + int fd = -1; + + for (rp = res; rp; rp = rp->ai_next) { + fd = util::create_nonblock_socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (fd == -1) { + continue; + } + + if (rp->ai_family == AF_INET6) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + continue; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + continue; + } + } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + continue; + } + + if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; + } + + close(fd); + } + + if (!rp) { + std::cerr << "Could not bind" << std::endl; + return -1; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + return -1; + } + + fd_set_recv_ecn(fd, rp->ai_family); + fd_set_ip_mtu_discover(fd, rp->ai_family); + fd_set_ip_dontfrag(fd, family); + + socklen_t len = sizeof(local_addr.su.storage); + if (getsockname(fd, &local_addr.su.sa, &len) == -1) { + std::cerr << "getsockname: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + local_addr.len = len; + local_addr.ifindex = 0; + + return fd; +} + +} // namespace + +namespace { +int add_endpoint(std::vector<Endpoint> &endpoints, const char *addr, + const char *port, int af) { + Address dest; + auto fd = create_sock(dest, addr, port, af); + if (fd == -1) { + return -1; + } + + endpoints.emplace_back(); + auto &ep = endpoints.back(); + ep.addr = dest; + ep.fd = fd; + ev_io_init(&ep.rev, sreadcb, 0, EV_READ); + + return 0; +} +} // namespace + +namespace { +int add_endpoint(std::vector<Endpoint> &endpoints, const Address &addr) { + auto fd = util::create_nonblock_socket(addr.su.sa.sa_family, SOCK_DGRAM, 0); + if (fd == -1) { + std::cerr << "socket: " << strerror(errno) << std::endl; + return -1; + } + + int val = 1; + if (addr.su.sa.sa_family == AF_INET6) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + + if (bind(fd, &addr.su.sa, addr.len) == -1) { + std::cerr << "bind: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + return -1; + } + + fd_set_recv_ecn(fd, addr.su.sa.sa_family); + fd_set_ip_mtu_discover(fd, addr.su.sa.sa_family); + fd_set_ip_dontfrag(fd, addr.su.sa.sa_family); + + endpoints.emplace_back(Endpoint{}); + auto &ep = endpoints.back(); + ep.addr = addr; + ep.fd = fd; + ev_io_init(&ep.rev, sreadcb, 0, EV_READ); + + return 0; +} +} // namespace + +int Server::init(const char *addr, const char *port) { + endpoints_.reserve(4); + + auto ready = false; + if (!util::numeric_host(addr, AF_INET6) && + add_endpoint(endpoints_, addr, port, AF_INET) == 0) { + ready = true; + } + if (!util::numeric_host(addr, AF_INET) && + add_endpoint(endpoints_, addr, port, AF_INET6) == 0) { + ready = true; + } + if (!ready) { + return -1; + } + + if (config.preferred_ipv4_addr.len && + add_endpoint(endpoints_, config.preferred_ipv4_addr) != 0) { + return -1; + } + if (config.preferred_ipv6_addr.len && + add_endpoint(endpoints_, config.preferred_ipv6_addr) != 0) { + return -1; + } + + for (auto &ep : endpoints_) { + ep.server = this; + ep.rev.data = &ep; + + ev_io_set(&ep.rev, ep.fd, EV_READ); + + ev_io_start(loop_, &ep.rev); + } + + ev_signal_start(loop_, &sigintev_); + + return 0; +} + +int Server::on_read(Endpoint &ep) { + sockaddr_union su; + std::array<uint8_t, 64_k> buf; + ngtcp2_pkt_hd hd; + size_t pktcnt = 0; + ngtcp2_pkt_info pi; + + iovec msg_iov; + msg_iov.iov_base = buf.data(); + msg_iov.iov_len = buf.size(); + + msghdr msg{}; + msg.msg_name = &su; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t + msg_ctrl[CMSG_SPACE(sizeof(uint8_t)) + CMSG_SPACE(sizeof(in6_pktinfo))]; + msg.msg_control = msg_ctrl; + + for (; pktcnt < 10;) { + msg.msg_namelen = sizeof(su); + msg.msg_controllen = sizeof(msg_ctrl); + + auto nread = recvmsg(ep.fd, &msg, 0); + if (nread == -1) { + if (!(errno == EAGAIN || errno == ENOTCONN)) { + std::cerr << "recvmsg: " << strerror(errno) << std::endl; + } + return 0; + } + + ++pktcnt; + + pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family); + auto local_addr = msghdr_get_local_addr(&msg, su.storage.ss_family); + if (!local_addr) { + std::cerr << "Unable to obtain local address" << std::endl; + continue; + } + + set_port(*local_addr, ep.addr); + + if (!config.quiet) { + std::array<char, IF_NAMESIZE> ifname; + std::cerr << "Received packet: local=" + << util::straddr(&local_addr->su.sa, local_addr->len) + << " remote=" << util::straddr(&su.sa, msg.msg_namelen) + << " if=" << if_indextoname(local_addr->ifindex, ifname.data()) + << " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread + << " bytes" << std::endl; + } + + if (debug::packet_lost(config.rx_loss_prob)) { + if (!config.quiet) { + std::cerr << "** Simulated incoming packet loss **" << std::endl; + } + continue; + } + + if (nread == 0) { + continue; + } + + ngtcp2_version_cid vc; + + switch (auto rv = ngtcp2_pkt_decode_version_cid(&vc, buf.data(), nread, + NGTCP2_SV_SCIDLEN); + rv) { + case 0: + break; + case NGTCP2_ERR_VERSION_NEGOTIATION: + send_version_negotiation(vc.version, vc.scid, vc.scidlen, vc.dcid, + vc.dcidlen, ep, *local_addr, &su.sa, + msg.msg_namelen); + continue; + default: + std::cerr << "Could not decode version and CID from QUIC packet header: " + << ngtcp2_strerror(rv) << std::endl; + continue; + } + + auto dcid_key = util::make_cid_key(vc.dcid, vc.dcidlen); + + auto handler_it = handlers_.find(dcid_key); + if (handler_it == std::end(handlers_)) { + switch (auto rv = ngtcp2_accept(&hd, buf.data(), nread); rv) { + case 0: + break; + case NGTCP2_ERR_RETRY: + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3); + continue; + default: + if (!config.quiet) { + std::cerr << "Unexpected packet received: length=" << nread + << std::endl; + } + continue; + } + + ngtcp2_cid ocid; + ngtcp2_cid *pocid = nullptr; + + assert(hd.type == NGTCP2_PKT_INITIAL); + + if (config.validate_addr || hd.token.len) { + std::cerr << "Perform stateless address validation" << std::endl; + if (hd.token.len == 0) { + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3); + continue; + } + + if (hd.token.base[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY && + hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN) { + send_stateless_connection_close(&hd, ep, *local_addr, &su.sa, + msg.msg_namelen); + continue; + } + + switch (hd.token.base[0]) { + case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY: + if (verify_retry_token(&ocid, &hd, &su.sa, msg.msg_namelen) != 0) { + send_stateless_connection_close(&hd, ep, *local_addr, &su.sa, + msg.msg_namelen); + continue; + } + pocid = &ocid; + break; + case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR: + if (verify_token(&hd, &su.sa, msg.msg_namelen) != 0) { + if (config.validate_addr) { + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, + nread * 3); + continue; + } + + hd.token.base = nullptr; + hd.token.len = 0; + } + break; + default: + if (!config.quiet) { + std::cerr << "Ignore unrecognized token" << std::endl; + } + if (config.validate_addr) { + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, + nread * 3); + continue; + } + + hd.token.base = nullptr; + hd.token.len = 0; + break; + } + } + + auto h = std::make_unique<Handler>(loop_, this); + if (h->init(ep, *local_addr, &su.sa, msg.msg_namelen, &hd.scid, &hd.dcid, + pocid, hd.token.base, hd.token.len, hd.version, + tls_ctx_) != 0) { + continue; + } + + switch (h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi, + buf.data(), nread)) { + case 0: + break; + case NETWORK_ERR_RETRY: + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3); + continue; + default: + continue; + } + + switch (h->on_write()) { + case 0: + break; + default: + continue; + } + + std::array<ngtcp2_cid, 2> scids; + auto conn = h->conn(); + + auto num_scid = ngtcp2_conn_get_num_scid(conn); + + assert(num_scid <= scids.size()); + + ngtcp2_conn_get_scid(conn, scids.data()); + + for (size_t i = 0; i < num_scid; ++i) { + handlers_.emplace(util::make_cid_key(&scids[i]), h.get()); + } + + handlers_.emplace(dcid_key, h.get()); + + h.release(); + + continue; + } + + auto h = (*handler_it).second; + auto conn = h->conn(); + if (ngtcp2_conn_is_in_closing_period(conn)) { + // TODO do exponential backoff. + switch (h->send_conn_close()) { + case 0: + break; + default: + remove(h); + } + continue; + } + if (ngtcp2_conn_is_in_draining_period(conn)) { + continue; + } + + if (auto rv = h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi, + buf.data(), nread); + rv != 0) { + if (rv != NETWORK_ERR_CLOSE_WAIT) { + remove(h); + } + continue; + } + + h->signal_write(); + } + + return 0; +} + +namespace { +uint32_t generate_reserved_version(const sockaddr *sa, socklen_t salen, + uint32_t version) { + uint32_t h = 0x811C9DC5u; + const uint8_t *p = (const uint8_t *)sa; + const uint8_t *ep = p + salen; + for (; p != ep; ++p) { + h ^= *p; + h *= 0x01000193u; + } + version = htonl(version); + p = (const uint8_t *)&version; + ep = p + sizeof(version); + for (; p != ep; ++p) { + h ^= *p; + h *= 0x01000193u; + } + h &= 0xf0f0f0f0u; + h |= 0x0a0a0a0au; + return h; +} +} // namespace + +int Server::send_version_negotiation(uint32_t version, const uint8_t *dcid, + size_t dcidlen, const uint8_t *scid, + size_t scidlen, Endpoint &ep, + const Address &local_addr, + const sockaddr *sa, socklen_t salen) { + Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE}; + std::array<uint32_t, 1 + max_preferred_versionslen> sv; + + auto p = std::begin(sv); + + *p++ = generate_reserved_version(sa, salen, version); + + if (config.preferred_versions.empty()) { + *p++ = NGTCP2_PROTO_VER_V1; + } else { + for (auto v : config.preferred_versions) { + *p++ = v; + } + } + + auto nwrite = ngtcp2_pkt_write_version_negotiation( + buf.wpos(), buf.left(), + std::uniform_int_distribution<uint8_t>( + 0, std::numeric_limits<uint8_t>::max())(randgen), + dcid, dcidlen, scid, scidlen, sv.data(), p - std::begin(sv)); + if (nwrite < 0) { + std::cerr << "ngtcp2_pkt_write_version_negotiation: " + << ngtcp2_strerror(nwrite) << std::endl; + return -1; + } + + buf.push(nwrite); + + ngtcp2_addr laddr{ + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }; + ngtcp2_addr raddr{ + const_cast<sockaddr *>(sa), + salen, + }; + + if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) != + NETWORK_ERR_OK) { + return -1; + } + + return 0; +} + +int Server::send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep, + const Address &local_addr, const sockaddr *sa, + socklen_t salen, size_t max_pktlen) { + std::array<char, NI_MAXHOST> host; + std::array<char, NI_MAXSERV> port; + + if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), + port.size(), NI_NUMERICHOST | NI_NUMERICSERV); + rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + if (!config.quiet) { + std::cerr << "Sending Retry packet to [" << host.data() + << "]:" << port.data() << std::endl; + } + + ngtcp2_cid scid; + + scid.datalen = NGTCP2_SV_SCIDLEN; + if (util::generate_secure_random(scid.data, scid.datalen) != 0) { + return -1; + } + + std::array<uint8_t, NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN> token; + + auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + auto tokenlen = ngtcp2_crypto_generate_retry_token( + token.data(), config.static_secret.data(), config.static_secret.size(), + chd->version, sa, salen, &scid, &chd->dcid, t); + if (tokenlen < 0) { + return -1; + } + + if (!config.quiet) { + std::cerr << "Generated address validation token:" << std::endl; + util::hexdump(stderr, token.data(), tokenlen); + } + + Buffer buf{ + std::min(static_cast<size_t>(NGTCP2_MAX_UDP_PAYLOAD_SIZE), max_pktlen)}; + + auto nwrite = ngtcp2_crypto_write_retry(buf.wpos(), buf.left(), chd->version, + &chd->scid, &scid, &chd->dcid, + token.data(), tokenlen); + if (nwrite < 0) { + std::cerr << "ngtcp2_crypto_write_retry failed" << std::endl; + return -1; + } + + buf.push(nwrite); + + ngtcp2_addr laddr{ + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }; + ngtcp2_addr raddr{ + const_cast<sockaddr *>(sa), + salen, + }; + + if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) != + NETWORK_ERR_OK) { + return -1; + } + + return 0; +} + +int Server::send_stateless_connection_close(const ngtcp2_pkt_hd *chd, + Endpoint &ep, + const Address &local_addr, + const sockaddr *sa, + socklen_t salen) { + Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE}; + + auto nwrite = ngtcp2_crypto_write_connection_close( + buf.wpos(), buf.left(), chd->version, &chd->scid, &chd->dcid, + NGTCP2_INVALID_TOKEN, nullptr, 0); + if (nwrite < 0) { + std::cerr << "ngtcp2_crypto_write_connection_close failed" << std::endl; + return -1; + } + + buf.push(nwrite); + + ngtcp2_addr laddr{ + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }; + ngtcp2_addr raddr{ + const_cast<sockaddr *>(sa), + salen, + }; + + if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) != + NETWORK_ERR_OK) { + return -1; + } + + return 0; +} + +int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, + const sockaddr *sa, socklen_t salen) { + std::array<char, NI_MAXHOST> host; + std::array<char, NI_MAXSERV> port; + + if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), + port.size(), NI_NUMERICHOST | NI_NUMERICSERV); + rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + if (!config.quiet) { + std::cerr << "Verifying Retry token from [" << host.data() + << "]:" << port.data() << std::endl; + util::hexdump(stderr, hd->token.base, hd->token.len); + } + + auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + if (ngtcp2_crypto_verify_retry_token( + ocid, hd->token.base, hd->token.len, config.static_secret.data(), + config.static_secret.size(), hd->version, sa, salen, &hd->dcid, + 10 * NGTCP2_SECONDS, t) != 0) { + std::cerr << "Could not verify Retry token" << std::endl; + + return -1; + } + + if (!config.quiet) { + std::cerr << "Token was successfully validated" << std::endl; + } + + return 0; +} + +int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa, + socklen_t salen) { + std::array<char, NI_MAXHOST> host; + std::array<char, NI_MAXSERV> port; + + if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), + port.size(), NI_NUMERICHOST | NI_NUMERICSERV); + rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + if (!config.quiet) { + std::cerr << "Verifying token from [" << host.data() << "]:" << port.data() + << std::endl; + util::hexdump(stderr, hd->token.base, hd->token.len); + } + + auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + if (ngtcp2_crypto_verify_regular_token(hd->token.base, hd->token.len, + config.static_secret.data(), + config.static_secret.size(), sa, salen, + 3600 * NGTCP2_SECONDS, t) != 0) { + std::cerr << "Could not verify token" << std::endl; + + return -1; + } + + if (!config.quiet) { + std::cerr << "Token was successfully validated" << std::endl; + } + + return 0; +} + +int Server::send_packet(Endpoint &ep, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen) { + auto no_gso = false; + auto [_, rv] = send_packet(ep, no_gso, local_addr, remote_addr, ecn, data, + datalen, datalen); + + return rv; +} + +std::pair<size_t, int> +Server::send_packet(Endpoint &ep, bool &no_gso, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen, size_t gso_size) { + assert(gso_size); + + if (debug::packet_lost(config.tx_loss_prob)) { + if (!config.quiet) { + std::cerr << "** Simulated outgoing packet loss **" << std::endl; + } + return {0, NETWORK_ERR_OK}; + } + + if (no_gso && datalen > gso_size) { + size_t nsent = 0; + + for (auto p = data; p < data + datalen; p += gso_size) { + auto len = std::min(gso_size, static_cast<size_t>(data + datalen - p)); + + auto [n, rv] = + send_packet(ep, no_gso, local_addr, remote_addr, ecn, p, len, len); + if (rv != 0) { + return {nsent, rv}; + } + + nsent += n; + } + + return {nsent, 0}; + } + + iovec msg_iov; + msg_iov.iov_base = const_cast<uint8_t *>(data); + msg_iov.iov_len = datalen; + + msghdr msg{}; + msg.msg_name = const_cast<sockaddr *>(remote_addr.addr); + msg.msg_namelen = remote_addr.addrlen; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t + msg_ctrl[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(in6_pktinfo))]; + + memset(msg_ctrl, 0, sizeof(msg_ctrl)); + + msg.msg_control = msg_ctrl; + msg.msg_controllen = sizeof(msg_ctrl); + + size_t controllen = 0; + + auto cm = CMSG_FIRSTHDR(&msg); + + switch (local_addr.addr->sa_family) { + case AF_INET: { + controllen += CMSG_SPACE(sizeof(in_pktinfo)); + cm->cmsg_level = IPPROTO_IP; + cm->cmsg_type = IP_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); + auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cm)); + memset(pktinfo, 0, sizeof(in_pktinfo)); + auto addrin = reinterpret_cast<sockaddr_in *>(local_addr.addr); + pktinfo->ipi_spec_dst = addrin->sin_addr; + break; + } + case AF_INET6: { + controllen += CMSG_SPACE(sizeof(in6_pktinfo)); + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); + auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cm)); + memset(pktinfo, 0, sizeof(in6_pktinfo)); + auto addrin = reinterpret_cast<sockaddr_in6 *>(local_addr.addr); + pktinfo->ipi6_addr = addrin->sin6_addr; + break; + } + default: + assert(0); + } + +#ifdef UDP_SEGMENT + if (datalen > gso_size) { + controllen += CMSG_SPACE(sizeof(uint16_t)); + cm = CMSG_NXTHDR(&msg, cm); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *(reinterpret_cast<uint16_t *>(CMSG_DATA(cm))) = gso_size; + } +#endif // UDP_SEGMENT + + msg.msg_controllen = controllen; + + if (ep.ecn != ecn) { + ep.ecn = ecn; + fd_set_ecn(ep.fd, ep.addr.su.storage.ss_family, ecn); + } + + ssize_t nwrite = 0; + + do { + nwrite = sendmsg(ep.fd, &msg, 0); + } while (nwrite == -1 && errno == EINTR); + + if (nwrite == -1) { + switch (errno) { + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif // EAGAIN != EWOULDBLOCK + return {0, NETWORK_ERR_SEND_BLOCKED}; +#ifdef UDP_SEGMENT + case EIO: + if (datalen > gso_size) { + // GSO failure; send each packet in a separate sendmsg call. + std::cerr << "sendmsg: disabling GSO due to " << strerror(errno) + << std::endl; + + no_gso = true; + + return send_packet(ep, no_gso, local_addr, remote_addr, ecn, data, + datalen, gso_size); + } + break; +#endif // UDP_SEGMENT + } + + std::cerr << "sendmsg: " << strerror(errno) << std::endl; + // TODO We have packet which is expected to fail to send (e.g., + // path validation to old path). + return {0, NETWORK_ERR_OK}; + } + + if (!config.quiet) { + std::cerr << "Sent packet: local=" + << util::straddr(local_addr.addr, local_addr.addrlen) + << " remote=" + << util::straddr(remote_addr.addr, remote_addr.addrlen) + << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite + << " bytes" << std::endl; + } + + return {nwrite, NETWORK_ERR_OK}; +} + +void Server::associate_cid(const ngtcp2_cid *cid, Handler *h) { + handlers_.emplace(util::make_cid_key(cid), h); +} + +void Server::dissociate_cid(const ngtcp2_cid *cid) { + handlers_.erase(util::make_cid_key(cid)); +} + +void Server::remove(const Handler *h) { + auto conn = h->conn(); + + handlers_.erase( + util::make_cid_key(ngtcp2_conn_get_client_initial_dcid(conn))); + + std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(conn)); + ngtcp2_conn_get_scid(conn, cids.data()); + + for (auto &cid : cids) { + handlers_.erase(util::make_cid_key(&cid)); + } + + delete h; +} + +namespace { +int parse_host_port(Address &dest, int af, const char *first, + const char *last) { + if (std::distance(first, last) == 0) { + return -1; + } + + const char *host_begin, *host_end, *it; + if (*first == '[') { + host_begin = first + 1; + it = std::find(host_begin, last, ']'); + if (it == last) { + return -1; + } + host_end = it; + ++it; + if (it == last || *it != ':') { + return -1; + } + } else { + host_begin = first; + it = std::find(host_begin, last, ':'); + if (it == last) { + return -1; + } + host_end = it; + } + + if (++it == last) { + return -1; + } + auto svc_begin = it; + + std::array<char, NI_MAXHOST> host; + *std::copy(host_begin, host_end, std::begin(host)) = '\0'; + + addrinfo hints{}, *res; + hints.ai_family = af; + hints.ai_socktype = SOCK_DGRAM; + + if (auto rv = getaddrinfo(host.data(), svc_begin, &hints, &res); rv != 0) { + std::cerr << "getaddrinfo: [" << host.data() << "]:" << svc_begin << ": " + << gai_strerror(rv) << std::endl; + return -1; + } + + dest.len = res->ai_addrlen; + memcpy(&dest.su, res->ai_addr, res->ai_addrlen); + + freeaddrinfo(res); + + return 0; +} +} // namespace + +namespace { +void print_usage() { + std::cerr << "Usage: server [OPTIONS] <ADDR> <PORT> <PRIVATE_KEY_FILE> " + "<CERTIFICATE_FILE>" + << std::endl; +} +} // namespace + +namespace { +void config_set_default(Config &config) { + config = Config{}; + config.tx_loss_prob = 0.; + config.rx_loss_prob = 0.; + config.ciphers = util::crypto_default_ciphers(); + config.groups = util::crypto_default_groups(); + config.timeout = 30 * NGTCP2_SECONDS; + { + auto path = realpath(".", nullptr); + assert(path); + config.htdocs = path; + free(path); + } + config.mime_types_file = "/etc/mime.types"sv; + config.max_data = 1_m; + config.max_stream_data_bidi_local = 256_k; + config.max_stream_data_bidi_remote = 256_k; + config.max_stream_data_uni = 256_k; + config.max_window = 6_m; + config.max_stream_window = 6_m; + config.max_streams_bidi = 100; + config.max_streams_uni = 3; + config.max_dyn_length = 20_m; + config.cc_algo = NGTCP2_CC_ALGO_CUBIC; + config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; + config.max_gso_dgrams = 64; + config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT; + config.ack_thresh = 2; +} +} // namespace + +namespace { +void print_help() { + print_usage(); + + config_set_default(config); + + std::cout << R"( + <ADDR> Address to listen to. '*' binds to any address. + <PORT> Port + <PRIVATE_KEY_FILE> + Path to private key file + <CERTIFICATE_FILE> + Path to certificate file +Options: + -t, --tx-loss=<P> + The probability of losing outgoing packets. <P> must be + [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 + means 100% packet loss. + -r, --rx-loss=<P> + The probability of losing incoming packets. <P> must be + [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 + means 100% packet loss. + --ciphers=<CIPHERS> + Specify the cipher suite list to enable. + Default: )" + << config.ciphers << R"( + --groups=<GROUPS> + Specify the supported groups. + Default: )" + << config.groups << R"( + -d, --htdocs=<PATH> + Specify document root. If this option is not specified, + the document root is the current working directory. + -q, --quiet Suppress debug output. + -s, --show-secret + Print out secrets unless --quiet is used. + --timeout=<DURATION> + Specify idle timeout. + Default: )" + << util::format_duration(config.timeout) << R"( + -V, --validate-addr + Perform address validation. + --preferred-ipv4-addr=<ADDR>:<PORT> + Specify preferred IPv4 address and port. + --preferred-ipv6-addr=<ADDR>:<PORT> + Specify preferred IPv6 address and port. A numeric IPv6 + address must be enclosed by '[' and ']' (e.g., + [::1]:8443) + --mime-types-file=<PATH> + Path to file that contains MIME media types and the + extensions. + Default: )" + << config.mime_types_file << R"( + --early-response + Start sending response when it receives HTTP header + fields without waiting for request body. If HTTP + response data is written before receiving request body, + STOP_SENDING is sent. + --verify-client + Request a client certificate. At the moment, we just + request a certificate and no verification is done. + --qlog-dir=<PATH> + Path to the directory where qlog file is stored. The + file name of each qlog is the Source Connection ID of + server. + --no-quic-dump + Disables printing QUIC STREAM and CRYPTO frame data out. + --no-http-dump + Disables printing HTTP response body out. + --max-data=<SIZE> + The initial connection-level flow control window. + Default: )" + << util::format_uint_iec(config.max_data) << R"( + --max-stream-data-bidi-local=<SIZE> + The initial stream-level flow control window for a + bidirectional stream that the local endpoint initiates. + Default: )" + << util::format_uint_iec(config.max_stream_data_bidi_local) << R"( + --max-stream-data-bidi-remote=<SIZE> + The initial stream-level flow control window for a + bidirectional stream that the remote endpoint initiates. + Default: )" + << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"( + --max-stream-data-uni=<SIZE> + The initial stream-level flow control window for a + unidirectional stream. + Default: )" + << util::format_uint_iec(config.max_stream_data_uni) << R"( + --max-streams-bidi=<N> + The number of the concurrent bidirectional streams. + Default: )" + << config.max_streams_bidi << R"( + --max-streams-uni=<N> + The number of the concurrent unidirectional streams. + Default: )" + << config.max_streams_uni << R"( + --max-dyn-length=<SIZE> + The maximum length of a dynamically generated content. + Default: )" + << util::format_uint_iec(config.max_dyn_length) << R"( + --cc=(cubic|reno|bbr|bbr2) + The name of congestion controller algorithm. + Default: )" + << util::strccalgo(config.cc_algo) << R"( + --initial-rtt=<DURATION> + Set an initial RTT. + Default: )" + << util::format_duration(config.initial_rtt) << R"( + --max-udp-payload-size=<SIZE> + Override maximum UDP payload size that server transmits. + --max-window=<SIZE> + Maximum connection-level flow control window size. The + window auto-tuning is enabled if nonzero value is given, + and window size is scaled up to this value. + Default: )" + << util::format_uint_iec(config.max_window) << R"( + --max-stream-window=<SIZE> + Maximum stream-level flow control window size. The + window auto-tuning is enabled if nonzero value is given, + and window size is scaled up to this value. + Default: )" + << util::format_uint_iec(config.max_stream_window) << R"( + --send-trailers + Send trailer fields. + --max-gso-dgrams=<N> + Maximum number of UDP datagrams that are sent in a + single GSO sendmsg call. + Default: )" + << config.max_gso_dgrams << R"( + --handshake-timeout=<DURATION> + Set the QUIC handshake timeout. + Default: )" + << util::format_duration(config.handshake_timeout) << R"( + --preferred-versions=<HEX>[[,<HEX>]...] + Specify QUIC versions in hex string in the order of + preference. Server negotiates one of those versions if + client initially selects a less preferred version. + These versions must be supported by libngtcp2. Instead + of specifying hex string, there are special aliases + available: "v1" indicates QUIC v1, and "v2draft" + indicates QUIC v2 draft. + --other-versions=<HEX>[[,<HEX>]...] + Specify QUIC versions in hex string that are sent in + other_versions field of version_information transport + parameter. This list can include a version which is not + supported by libngtcp2. Instead of specifying hex + string, there are special aliases available: "v1" + indicates QUIC v1, and "v2draft" indicates QUIC v2 + draft. + --no-pmtud Disables Path MTU Discovery. + --ack-thresh=<N> + The minimum number of the received ACK eliciting packets + that triggers immediate acknowledgement. + Default: )" + << config.ack_thresh << R"( + -h, --help Display this help and exit. + +--- + + The <SIZE> argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The <DURATION> argument is an integer and an optional unit (e.g., 1s + is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms, + us, or ns (hours, minutes, seconds, milliseconds, microseconds, and + nanoseconds respectively). If a unit is omitted, a second is used + as unit. + + The <HEX> argument is an hex string which must start with "0x" + (e.g., 0x00000001).)" + << std::endl; +} +} // namespace + +std::ofstream keylog_file; + +int main(int argc, char **argv) { + config_set_default(config); + + for (;;) { + static int flag = 0; + constexpr static option long_opts[] = { + {"help", no_argument, nullptr, 'h'}, + {"tx-loss", required_argument, nullptr, 't'}, + {"rx-loss", required_argument, nullptr, 'r'}, + {"htdocs", required_argument, nullptr, 'd'}, + {"quiet", no_argument, nullptr, 'q'}, + {"show-secret", no_argument, nullptr, 's'}, + {"validate-addr", no_argument, nullptr, 'V'}, + {"ciphers", required_argument, &flag, 1}, + {"groups", required_argument, &flag, 2}, + {"timeout", required_argument, &flag, 3}, + {"preferred-ipv4-addr", required_argument, &flag, 4}, + {"preferred-ipv6-addr", required_argument, &flag, 5}, + {"mime-types-file", required_argument, &flag, 6}, + {"early-response", no_argument, &flag, 7}, + {"verify-client", no_argument, &flag, 8}, + {"qlog-dir", required_argument, &flag, 9}, + {"no-quic-dump", no_argument, &flag, 10}, + {"no-http-dump", no_argument, &flag, 11}, + {"max-data", required_argument, &flag, 12}, + {"max-stream-data-bidi-local", required_argument, &flag, 13}, + {"max-stream-data-bidi-remote", required_argument, &flag, 14}, + {"max-stream-data-uni", required_argument, &flag, 15}, + {"max-streams-bidi", required_argument, &flag, 16}, + {"max-streams-uni", required_argument, &flag, 17}, + {"max-dyn-length", required_argument, &flag, 18}, + {"cc", required_argument, &flag, 19}, + {"initial-rtt", required_argument, &flag, 20}, + {"max-udp-payload-size", required_argument, &flag, 21}, + {"send-trailers", no_argument, &flag, 22}, + {"max-window", required_argument, &flag, 23}, + {"max-stream-window", required_argument, &flag, 24}, + {"max-gso-dgrams", required_argument, &flag, 25}, + {"handshake-timeout", required_argument, &flag, 26}, + {"preferred-versions", required_argument, &flag, 27}, + {"other-versions", required_argument, &flag, 28}, + {"no-pmtud", no_argument, &flag, 29}, + {"ack-thresh", required_argument, &flag, 30}, + {nullptr, 0, nullptr, 0}}; + + auto optidx = 0; + auto c = getopt_long(argc, argv, "d:hqr:st:V", long_opts, &optidx); + if (c == -1) { + break; + } + switch (c) { + case 'd': { + // --htdocs + auto path = realpath(optarg, nullptr); + if (path == nullptr) { + std::cerr << "path: invalid path " << std::quoted(optarg) << std::endl; + exit(EXIT_FAILURE); + } + config.htdocs = path; + free(path); + break; + } + case 'h': + // --help + print_help(); + exit(EXIT_SUCCESS); + case 'q': + // --quiet + config.quiet = true; + break; + case 'r': + // --rx-loss + config.rx_loss_prob = strtod(optarg, nullptr); + break; + case 's': + // --show-secret + config.show_secret = true; + break; + case 't': + // --tx-loss + config.tx_loss_prob = strtod(optarg, nullptr); + break; + case 'V': + // --validate-addr + config.validate_addr = true; + break; + case '?': + print_usage(); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // --ciphers + config.ciphers = optarg; + break; + case 2: + // --groups + config.groups = optarg; + break; + case 3: + // --timeout + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "timeout: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.timeout = *t; + } + break; + case 4: + // --preferred-ipv4-addr + if (parse_host_port(config.preferred_ipv4_addr, AF_INET, optarg, + optarg + strlen(optarg)) != 0) { + std::cerr << "preferred-ipv4-addr: could not use " + << std::quoted(optarg) << std::endl; + exit(EXIT_FAILURE); + } + break; + case 5: + // --preferred-ipv6-addr + if (parse_host_port(config.preferred_ipv6_addr, AF_INET6, optarg, + optarg + strlen(optarg)) != 0) { + std::cerr << "preferred-ipv6-addr: could not use " + << std::quoted(optarg) << std::endl; + exit(EXIT_FAILURE); + } + break; + case 6: + // --mime-types-file + config.mime_types_file = optarg; + break; + case 7: + // --early-response + config.early_response = true; + break; + case 8: + // --verify-client + config.verify_client = true; + break; + case 9: + // --qlog-dir + config.qlog_dir = optarg; + break; + case 10: + // --no-quic-dump + config.no_quic_dump = true; + break; + case 11: + // --no-http-dump + config.no_http_dump = true; + break; + case 12: + // --max-data + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-data: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_data = *n; + } + break; + case 13: + // --max-stream-data-bidi-local + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-bidi-local: invalid argument" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_bidi_local = *n; + } + break; + case 14: + // --max-stream-data-bidi-remote + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-bidi-remote: invalid argument" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_bidi_remote = *n; + } + break; + case 15: + // --max-stream-data-uni + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-uni: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_uni = *n; + } + break; + case 16: + // --max-streams-bidi + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-streams-bidi: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_streams_bidi = *n; + } + break; + case 17: + // --max-streams-uni + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-streams-uni: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_streams_uni = *n; + } + break; + case 18: + // --max-dyn-length + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-dyn-length: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_dyn_length = *n; + } + break; + case 19: + // --cc + if (strcmp("cubic", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_CUBIC; + break; + } + if (strcmp("reno", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_RENO; + break; + } + if (strcmp("bbr", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_BBR; + break; + } + if (strcmp("bbr2", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_BBR2; + break; + } + std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl; + exit(EXIT_FAILURE); + case 20: + // --initial-rtt + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "initial-rtt: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.initial_rtt = *t; + } + break; + case 21: + // --max-udp-payload-size + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-udp-payload-size: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 64_k) { + std::cerr << "max-udp-payload-size: must not exceed 65536" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_udp_payload_size = *n; + } + break; + case 22: + // --send-trailers + config.send_trailers = true; + break; + case 23: + // --max-window + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-window: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_window = *n; + } + break; + case 24: + // --max-stream-window + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-window: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_window = *n; + } + break; + case 25: + // --max-gso-dgrams + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-gso-dgrams: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_gso_dgrams = *n; + } + break; + case 26: + // --handshake-timeout + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "handshake-timeout: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.handshake_timeout = *t; + } + break; + case 27: { + // --preferred-versions + auto l = util::split_str(optarg); + if (l.size() > max_preferred_versionslen) { + std::cerr << "preferred-versions: too many versions > " + << max_preferred_versionslen << std::endl; + } + config.preferred_versions.resize(l.size()); + auto it = std::begin(config.preferred_versions); + for (const auto &k : l) { + if (k == "v1"sv) { + *it++ = NGTCP2_PROTO_VER_V1; + continue; + } + if (k == "v2draft"sv) { + *it++ = NGTCP2_PROTO_VER_V2_DRAFT; + continue; + } + auto rv = util::parse_version(k); + if (!rv) { + std::cerr << "preferred-versions: invalid version " + << std::quoted(k) << std::endl; + exit(EXIT_FAILURE); + } + if (!ngtcp2_is_supported_version(*rv)) { + std::cerr << "preferred-versions: unsupported version " + << std::quoted(k) << std::endl; + exit(EXIT_FAILURE); + } + *it++ = *rv; + } + break; + } + case 28: { + // --other-versions + auto l = util::split_str(optarg); + config.other_versions.resize(l.size()); + auto it = std::begin(config.other_versions); + for (const auto &k : l) { + if (k == "v1"sv) { + *it++ = NGTCP2_PROTO_VER_V1; + continue; + } + if (k == "v2draft"sv) { + *it++ = NGTCP2_PROTO_VER_V2_DRAFT; + continue; + } + auto rv = util::parse_version(k); + if (!rv) { + std::cerr << "other-versions: invalid version " << std::quoted(k) + << std::endl; + exit(EXIT_FAILURE); + } + *it++ = *rv; + } + break; + } + case 29: + // --no-pmtud + config.no_pmtud = true; + break; + case 30: + // --ack-thresh + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "ack-thresh: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 100) { + std::cerr << "ack-thresh: must not exceed 100" << std::endl; + exit(EXIT_FAILURE); + } else { + config.ack_thresh = *n; + } + break; + } + break; + default: + break; + }; + } + + if (argc - optind < 4) { + std::cerr << "Too few arguments" << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + auto addr = argv[optind++]; + auto port = argv[optind++]; + auto private_key_file = argv[optind++]; + auto cert_file = argv[optind++]; + + if (auto n = util::parse_uint(port); !n) { + std::cerr << "port: invalid port number" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 65535) { + std::cerr << "port: must not exceed 65535" << std::endl; + exit(EXIT_FAILURE); + } else { + config.port = *n; + } + + if (auto mt = util::read_mime_types(config.mime_types_file); !mt) { + std::cerr << "mime-types-file: Could not read MIME media types file " + << std::quoted(config.mime_types_file) << std::endl; + } else { + config.mime_types = std::move(*mt); + } + + TLSServerContext tls_ctx; + + if (tls_ctx.init(private_key_file, cert_file, AppProtocol::HQ) != 0) { + exit(EXIT_FAILURE); + } + + if (config.htdocs.back() != '/') { + config.htdocs += '/'; + } + + std::cerr << "Using document root " << config.htdocs << std::endl; + + auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT); + + auto keylog_filename = getenv("SSLKEYLOGFILE"); + if (keylog_filename) { + keylog_file.open(keylog_filename, std::ios_base::app); + if (keylog_file) { + tls_ctx.enable_keylog(); + } + } + + if (util::generate_secret(config.static_secret.data(), + config.static_secret.size()) != 0) { + std::cerr << "Unable to generate static secret" << std::endl; + exit(EXIT_FAILURE); + } + + Server s(EV_DEFAULT, tls_ctx); + if (s.init(addr, port) != 0) { + exit(EXIT_FAILURE); + } + + ev_run(EV_DEFAULT, 0); + + s.disconnect(); + s.close(); + + return EXIT_SUCCESS; +} diff --git a/examples/h09server.h b/examples/h09server.h new file mode 100644 index 0000000..8ab11f5 --- /dev/null +++ b/examples/h09server.h @@ -0,0 +1,237 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SERVER_H +#define SERVER_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <vector> +#include <unordered_map> +#include <string> +#include <deque> +#include <string_view> +#include <memory> +#include <set> + +#include <ngtcp2/ngtcp2.h> +#include <ngtcp2/ngtcp2_crypto.h> +#include <nghttp3/nghttp3.h> + +#include <ev.h> + +#include "server_base.h" +#include "tls_server_context.h" +#include "network.h" +#include "shared.h" + +using namespace ngtcp2; + +struct HTTPHeader { + HTTPHeader(const std::string_view &name, const std::string_view &value) + : name(name), value(value) {} + + std::string_view name; + std::string_view value; +}; + +class Handler; +struct FileEntry; + +struct Stream { + Stream(int64_t stream_id, Handler *handler); + + int start_response(); + std::pair<FileEntry, int> open_file(const std::string &path); + void map_file(const FileEntry &fe); + int send_status_response(unsigned int status_code); + + int64_t stream_id; + Handler *handler; + // uri is request uri/path. + std::string uri; + std::string status_resp_body; + nghttp3_buf respbuf; + http_parser htp; + // eos gets true when one HTTP request message is seen. + bool eos; +}; + +struct StreamIDLess { + constexpr bool operator()(const Stream *lhs, const Stream *rhs) const { + return lhs->stream_id < rhs->stream_id; + } +}; + +class Server; + +// Endpoint is a local endpoint. +struct Endpoint { + Address addr; + ev_io rev; + Server *server; + int fd; + // ecn is the last ECN bits set to fd. + unsigned int ecn; +}; + +class Handler : public HandlerBase { +public: + Handler(struct ev_loop *loop, Server *server); + ~Handler(); + + int init(const Endpoint &ep, const Address &local_addr, const sockaddr *sa, + socklen_t salen, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_cid *ocid, const uint8_t *token, size_t tokenlen, + uint32_t version, TLSServerContext &tls_ctx); + + int on_read(const Endpoint &ep, const Address &local_addr, const sockaddr *sa, + socklen_t salen, const ngtcp2_pkt_info *pi, uint8_t *data, + size_t datalen); + int on_write(); + int write_streams(); + int feed_data(const Endpoint &ep, const Address &local_addr, + const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi, + uint8_t *data, size_t datalen); + void update_timer(); + int handle_expiry(); + void signal_write(); + int handshake_completed(); + + Server *server() const; + int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data, + size_t datalen); + int acked_stream_data_offset(int64_t stream_id, uint64_t offset, + uint64_t datalen); + uint32_t version() const; + void on_stream_open(int64_t stream_id); + int on_stream_close(int64_t stream_id, uint64_t app_error_code); + void start_draining_period(); + int start_closing_period(); + int handle_error(); + int send_conn_close(); + + int update_key(uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen); + + Stream *find_stream(int64_t stream_id); + int extend_max_stream_data(int64_t stream_id, uint64_t max_data); + void shutdown_read(int64_t stream_id, int app_error_code); + + void write_qlog(const void *data, size_t datalen); + void add_sendq(Stream *stream); + + void on_send_blocked(Endpoint &ep, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen, size_t gso_size); + void start_wev_endpoint(const Endpoint &ep); + int send_blocked_packet(); + +private: + struct ev_loop *loop_; + Server *server_; + ev_io wev_; + ev_timer timer_; + FILE *qlog_; + ngtcp2_cid scid_; + std::unordered_map<int64_t, std::unique_ptr<Stream>> streams_; + std::set<Stream *, StreamIDLess> sendq_; + // conn_closebuf_ contains a packet which contains CONNECTION_CLOSE. + // This packet is repeatedly sent as a response to the incoming + // packet in draining period. + std::unique_ptr<Buffer> conn_closebuf_; + // nkey_update_ is the number of key update occurred. + size_t nkey_update_; + bool no_gso_; + + struct { + bool send_blocked; + size_t num_blocked; + size_t num_blocked_sent; + // blocked field is effective only when send_blocked is true. + struct { + Endpoint *endpoint; + Address local_addr; + Address remote_addr; + unsigned int ecn; + const uint8_t *data; + size_t datalen; + size_t gso_size; + } blocked[2]; + std::unique_ptr<uint8_t[]> data; + } tx_; +}; + +class Server { +public: + Server(struct ev_loop *loop, TLSServerContext &tls_ctx); + ~Server(); + + int init(const char *addr, const char *port); + void disconnect(); + void close(); + + int on_read(Endpoint &ep); + int send_version_negotiation(uint32_t version, const uint8_t *dcid, + size_t dcidlen, const uint8_t *scid, + size_t scidlen, Endpoint &ep, + const Address &local_addr, const sockaddr *sa, + socklen_t salen); + int send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep, + const Address &local_addr, const sockaddr *sa, socklen_t salen, + size_t max_pktlen); + int send_stateless_connection_close(const ngtcp2_pkt_hd *chd, Endpoint &ep, + const Address &local_addr, + const sockaddr *sa, socklen_t salen); + int verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, + const sockaddr *sa, socklen_t salen); + int verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa, + socklen_t salen); + int send_packet(Endpoint &ep, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen); + std::pair<size_t, int> send_packet(Endpoint &ep, bool &no_gso, + const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, + unsigned int ecn, const uint8_t *data, + size_t datalen, size_t gso_size); + void remove(const Handler *h); + + void associate_cid(const ngtcp2_cid *cid, Handler *h); + void dissociate_cid(const ngtcp2_cid *cid); + +private: + std::unordered_map<std::string, Handler *> handlers_; + struct ev_loop *loop_; + std::vector<Endpoint> endpoints_; + TLSServerContext &tls_ctx_; + ev_signal sigintev_; +}; + +#endif // SERVER_H diff --git a/examples/http.cc b/examples/http.cc new file mode 100644 index 0000000..bfcd188 --- /dev/null +++ b/examples/http.cc @@ -0,0 +1,138 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "http.h" + +namespace ngtcp2 { + +namespace http { + +std::string get_reason_phrase(unsigned int status_code) { + switch (status_code) { + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + // case 306: return "(Unused)"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Payload Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Requested Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 421: + return "Misdirected Request"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 511: + return "Network Authentication Required"; + default: + return ""; + } +} + +} // namespace http + +} // namespace ngtcp2 diff --git a/examples/http.h b/examples/http.h new file mode 100644 index 0000000..6622b4c --- /dev/null +++ b/examples/http.h @@ -0,0 +1,44 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef HTTP_H +#define HTTP_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <string> + +namespace ngtcp2 { + +namespace http { + +std::string get_reason_phrase(unsigned int status_code); + +} // namespace http + +} // namespace ngtcp2 + +#endif // HTTP_H diff --git a/examples/network.h b/examples/network.h new file mode 100644 index 0000000..451e531 --- /dev/null +++ b/examples/network.h @@ -0,0 +1,80 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2016 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NETWORK_H +#define NETWORK_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <sys/types.h> +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif // HAVE_SYS_SOCKET_H +#include <sys/un.h> +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif // HAVE_NETINET_IN_H +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif // HAVE_ARPA_INET_H + +#include <array> + +#include <ngtcp2/ngtcp2.h> + +namespace ngtcp2 { + +enum network_error { + NETWORK_ERR_OK = 0, + NETWORK_ERR_FATAL = -10, + NETWORK_ERR_SEND_BLOCKED = -11, + NETWORK_ERR_CLOSE_WAIT = -12, + NETWORK_ERR_RETRY = -13, + NETWORK_ERR_DROP_CONN = -14, +}; + +union in_addr_union { + in_addr in; + in6_addr in6; +}; + +union sockaddr_union { + sockaddr_storage storage; + sockaddr sa; + sockaddr_in6 in6; + sockaddr_in in; +}; + +struct Address { + socklen_t len; + union sockaddr_union su; + uint32_t ifindex; +}; + +} // namespace ngtcp2 + +#endif // NETWORK_H diff --git a/examples/server.cc b/examples/server.cc new file mode 100644 index 0000000..30db269 --- /dev/null +++ b/examples/server.cc @@ -0,0 +1,3741 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include <chrono> +#include <cstdlib> +#include <cassert> +#include <cstring> +#include <iostream> +#include <algorithm> +#include <memory> +#include <fstream> +#include <iomanip> + +#include <unistd.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <netinet/udp.h> +#include <net/if.h> + +#include <http-parser/http_parser.h> + +#include "server.h" +#include "network.h" +#include "debug.h" +#include "util.h" +#include "shared.h" +#include "http.h" +#include "template.h" + +using namespace ngtcp2; +using namespace std::literals; + +namespace { +constexpr size_t NGTCP2_SV_SCIDLEN = 18; +} // namespace + +namespace { +constexpr size_t MAX_DYNBUFLEN = 10_m; +} // namespace + +namespace { +constexpr size_t max_preferred_versionslen = 4; +} // namespace + +namespace { +auto randgen = util::make_mt19937(); +} // namespace + +Config config{}; + +Stream::Stream(int64_t stream_id, Handler *handler) + : stream_id(stream_id), + handler(handler), + data(nullptr), + datalen(0), + dynresp(false), + dyndataleft(0), + dynbuflen(0) {} + +namespace { +constexpr auto NGTCP2_SERVER = "nghttp3/ngtcp2 server"sv; +} // namespace + +namespace { +std::string make_status_body(unsigned int status_code) { + auto status_string = util::format_uint(status_code); + auto reason_phrase = http::get_reason_phrase(status_code); + + std::string body; + body = "<html><head><title>"; + body += status_string; + body += ' '; + body += reason_phrase; + body += "</title></head><body><h1>"; + body += status_string; + body += ' '; + body += reason_phrase; + body += "</h1><hr><address>"; + body += NGTCP2_SERVER; + body += " at port "; + body += util::format_uint(config.port); + body += "</address>"; + body += "</body></html>"; + return body; +} +} // namespace + +struct Request { + std::string path; + struct { + int32_t urgency; + int inc; + } pri; +}; + +namespace { +Request request_path(const std::string_view &uri, bool is_connect) { + http_parser_url u; + Request req; + + req.pri.urgency = -1; + req.pri.inc = -1; + + http_parser_url_init(&u); + + if (auto rv = http_parser_parse_url(uri.data(), uri.size(), is_connect, &u); + rv != 0) { + return req; + } + + if (u.field_set & (1 << UF_PATH)) { + req.path = std::string(uri.data() + u.field_data[UF_PATH].off, + u.field_data[UF_PATH].len); + if (req.path.find('%') != std::string::npos) { + req.path = util::percent_decode(std::begin(req.path), std::end(req.path)); + } + if (!req.path.empty() && req.path.back() == '/') { + req.path += "index.html"; + } + } else { + req.path = "/index.html"; + } + + req.path = util::normalize_path(req.path); + if (req.path == "/") { + req.path = "/index.html"; + } + + if (u.field_set & (1 << UF_QUERY)) { + static constexpr auto urgency_prefix = "u="sv; + static constexpr auto inc_prefix = "i="sv; + auto q = std::string(uri.data() + u.field_data[UF_QUERY].off, + u.field_data[UF_QUERY].len); + for (auto p = std::begin(q); p != std::end(q);) { + if (util::istarts_with(p, std::end(q), std::begin(urgency_prefix), + std::end(urgency_prefix))) { + auto urgency_start = p + urgency_prefix.size(); + auto urgency_end = std::find(urgency_start, std::end(q), '&'); + if (urgency_start + 1 == urgency_end && '0' <= *urgency_start && + *urgency_start <= '7') { + req.pri.urgency = *urgency_start - '0'; + } + if (urgency_end == std::end(q)) { + break; + } + p = urgency_end + 1; + continue; + } + if (util::istarts_with(p, std::end(q), std::begin(inc_prefix), + std::end(inc_prefix))) { + auto inc_start = p + inc_prefix.size(); + auto inc_end = std::find(inc_start, std::end(q), '&'); + if (inc_start + 1 == inc_end && + (*inc_start == '0' || *inc_start == '1')) { + req.pri.inc = *inc_start - '0'; + } + if (inc_end == std::end(q)) { + break; + } + p = inc_end + 1; + continue; + } + + p = std::find(p, std::end(q), '&'); + if (p == std::end(q)) { + break; + } + ++p; + } + } + return req; +} +} // namespace + +enum FileEntryFlag { + FILE_ENTRY_TYPE_DIR = 0x1, +}; + +struct FileEntry { + uint64_t len; + void *map; + int fd; + uint8_t flags; +}; + +namespace { +std::unordered_map<std::string, FileEntry> file_cache; +} // namespace + +std::pair<FileEntry, int> Stream::open_file(const std::string &path) { + auto it = file_cache.find(path); + if (it != std::end(file_cache)) { + return {(*it).second, 0}; + } + + auto fd = open(path.c_str(), O_RDONLY); + if (fd == -1) { + return {{}, -1}; + } + + struct stat st {}; + if (fstat(fd, &st) != 0) { + close(fd); + return {{}, -1}; + } + + FileEntry fe{}; + if (st.st_mode & S_IFDIR) { + fe.flags |= FILE_ENTRY_TYPE_DIR; + fe.fd = -1; + close(fd); + } else { + fe.fd = fd; + fe.len = st.st_size; + fe.map = mmap(nullptr, fe.len, PROT_READ, MAP_SHARED, fd, 0); + if (fe.map == MAP_FAILED) { + std::cerr << "mmap: " << strerror(errno) << std::endl; + close(fd); + return {{}, -1}; + } + } + + file_cache.emplace(path, fe); + + return {std::move(fe), 0}; +} + +void Stream::map_file(const FileEntry &fe) { + data = static_cast<uint8_t *>(fe.map); + datalen = fe.len; +} + +int64_t Stream::find_dyn_length(const std::string_view &path) { + assert(path[0] == '/'); + + if (path.size() == 1) { + return -1; + } + + uint64_t n = 0; + + for (auto it = std::begin(path) + 1; it != std::end(path); ++it) { + if (*it < '0' || '9' < *it) { + return -1; + } + auto d = *it - '0'; + if (n > (((1ull << 62) - 1) - d) / 10) { + return -1; + } + n = n * 10 + d; + if (n > config.max_dyn_length) { + return -1; + } + } + + return static_cast<int64_t>(n); +} + +namespace { +nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec, + size_t veccnt, uint32_t *pflags, void *user_data, + void *stream_user_data) { + auto stream = static_cast<Stream *>(stream_user_data); + + vec[0].base = stream->data; + vec[0].len = stream->datalen; + *pflags |= NGHTTP3_DATA_FLAG_EOF; + if (config.send_trailers) { + *pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM; + } + + return 1; +} +} // namespace + +auto dyn_buf = std::make_unique<std::array<uint8_t, 16_k>>(); + +namespace { +nghttp3_ssize dyn_read_data(nghttp3_conn *conn, int64_t stream_id, + nghttp3_vec *vec, size_t veccnt, uint32_t *pflags, + void *user_data, void *stream_user_data) { + auto stream = static_cast<Stream *>(stream_user_data); + + if (stream->dynbuflen > MAX_DYNBUFLEN) { + return NGHTTP3_ERR_WOULDBLOCK; + } + + auto len = + std::min(dyn_buf->size(), static_cast<size_t>(stream->dyndataleft)); + + vec[0].base = dyn_buf->data(); + vec[0].len = len; + + stream->dynbuflen += len; + stream->dyndataleft -= len; + + if (stream->dyndataleft == 0) { + *pflags |= NGHTTP3_DATA_FLAG_EOF; + if (config.send_trailers) { + *pflags |= NGHTTP3_DATA_FLAG_NO_END_STREAM; + auto stream_id_str = util::format_uint(stream_id); + std::array<nghttp3_nv, 1> trailers{ + util::make_nv_nc("x-ngtcp2-stream-id"sv, stream_id_str), + }; + + if (auto rv = nghttp3_conn_submit_trailers( + conn, stream_id, trailers.data(), trailers.size()); + rv != 0) { + std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv) + << std::endl; + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + } + + return 1; +} +} // namespace + +void Stream::http_acked_stream_data(uint64_t datalen) { + if (!dynresp) { + return; + } + + assert(dynbuflen >= datalen); + + dynbuflen -= datalen; +} + +int Stream::send_status_response(nghttp3_conn *httpconn, + unsigned int status_code, + const std::vector<HTTPHeader> &extra_headers) { + status_resp_body = make_status_body(status_code); + + auto status_code_str = util::format_uint(status_code); + auto content_length_str = util::format_uint(status_resp_body.size()); + + std::vector<nghttp3_nv> nva(4 + extra_headers.size()); + nva[0] = util::make_nv_nc(":status"sv, status_code_str); + nva[1] = util::make_nv_nn("server"sv, NGTCP2_SERVER); + nva[2] = util::make_nv_nn("content-type"sv, "text/html; charset=utf-8"); + nva[3] = util::make_nv_nc("content-length"sv, content_length_str); + for (size_t i = 0; i < extra_headers.size(); ++i) { + auto &hdr = extra_headers[i]; + auto &nv = nva[4 + i]; + nv = util::make_nv_cc(hdr.name, hdr.value); + } + + data = (uint8_t *)status_resp_body.data(); + datalen = status_resp_body.size(); + + nghttp3_data_reader dr{}; + dr.read_data = read_data; + + if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(), + nva.size(), &dr); + rv != 0) { + std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + if (config.send_trailers) { + auto stream_id_str = util::format_uint(stream_id); + std::array<nghttp3_nv, 1> trailers{ + util::make_nv_nc("x-ngtcp2-stream-id"sv, stream_id_str), + }; + + if (auto rv = nghttp3_conn_submit_trailers( + httpconn, stream_id, trailers.data(), trailers.size()); + rv != 0) { + std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + } + + handler->shutdown_read(stream_id, NGHTTP3_H3_NO_ERROR); + + return 0; +} + +int Stream::send_redirect_response(nghttp3_conn *httpconn, + unsigned int status_code, + const std::string_view &path) { + return send_status_response(httpconn, status_code, {{"location", path}}); +} + +int Stream::start_response(nghttp3_conn *httpconn) { + // TODO This should be handled by nghttp3 + if (uri.empty() || method.empty()) { + return send_status_response(httpconn, 400); + } + + auto req = request_path(uri, method == "CONNECT"); + if (req.path.empty()) { + return send_status_response(httpconn, 400); + } + + auto dyn_len = find_dyn_length(req.path); + + int64_t content_length = -1; + nghttp3_data_reader dr{}; + auto content_type = "text/plain"sv; + + if (dyn_len == -1) { + auto path = config.htdocs + req.path; + auto [fe, rv] = open_file(path); + if (rv != 0) { + send_status_response(httpconn, 404); + return 0; + } + + if (fe.flags & FILE_ENTRY_TYPE_DIR) { + send_redirect_response(httpconn, 308, + path.substr(config.htdocs.size() - 1) + '/'); + return 0; + } + + content_length = fe.len; + + if (method != "HEAD") { + map_file(fe); + } + + dr.read_data = read_data; + + auto ext = std::end(req.path) - 1; + for (; ext != std::begin(req.path) && *ext != '.' && *ext != '/'; --ext) + ; + if (*ext == '.') { + ++ext; + auto it = config.mime_types.find(std::string{ext, std::end(req.path)}); + if (it != std::end(config.mime_types)) { + content_type = (*it).second; + } + } + } else { + content_length = dyn_len; + dynresp = true; + dr.read_data = dyn_read_data; + + if (method != "HEAD") { + datalen = dyn_len; + dyndataleft = dyn_len; + } + + content_type = "application/octet-stream"sv; + } + + auto content_length_str = util::format_uint(content_length); + + std::array<nghttp3_nv, 5> nva{ + util::make_nv_nn(":status"sv, "200"sv), + util::make_nv_nn("server"sv, NGTCP2_SERVER), + util::make_nv_nn("content-type"sv, content_type), + util::make_nv_nc("content-length"sv, content_length_str), + }; + + size_t nvlen = 4; + + std::string prival; + + if (req.pri.urgency != -1 || req.pri.inc != -1) { + nghttp3_pri pri; + + if (auto rv = nghttp3_conn_get_stream_priority(httpconn, &pri, stream_id); + rv != 0) { + std::cerr << "nghttp3_conn_get_stream_priority: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + if (req.pri.urgency != -1) { + pri.urgency = req.pri.urgency; + } + if (req.pri.inc != -1) { + pri.inc = req.pri.inc; + } + + if (auto rv = nghttp3_conn_set_stream_priority(httpconn, stream_id, &pri); + rv != 0) { + std::cerr << "nghttp3_conn_set_stream_priority: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + prival = "u="; + prival += pri.urgency + '0'; + prival += ",i"; + if (!pri.inc) { + prival += "=?0"; + } + + nva[nvlen++] = util::make_nv_nc("priority"sv, prival); + } + + if (!config.quiet) { + debug::print_http_response_headers(stream_id, nva.data(), nvlen); + } + + if (auto rv = nghttp3_conn_submit_response(httpconn, stream_id, nva.data(), + nvlen, &dr); + rv != 0) { + std::cerr << "nghttp3_conn_submit_response: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + if (config.send_trailers && dyn_len == -1) { + auto stream_id_str = util::format_uint(stream_id); + std::array<nghttp3_nv, 1> trailers{ + util::make_nv_nc("x-ngtcp2-stream-id"sv, stream_id_str), + }; + + if (auto rv = nghttp3_conn_submit_trailers( + httpconn, stream_id, trailers.data(), trailers.size()); + rv != 0) { + std::cerr << "nghttp3_conn_submit_trailers: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + } + + return 0; +} + +namespace { +void writecb(struct ev_loop *loop, ev_io *w, int revents) { + auto h = static_cast<Handler *>(w->data); + auto s = h->server(); + + switch (h->on_write()) { + case 0: + case NETWORK_ERR_CLOSE_WAIT: + return; + default: + s->remove(h); + } +} +} // namespace + +namespace { +void close_waitcb(struct ev_loop *loop, ev_timer *w, int revents) { + auto h = static_cast<Handler *>(w->data); + auto s = h->server(); + auto conn = h->conn(); + + if (ngtcp2_conn_is_in_closing_period(conn)) { + if (!config.quiet) { + std::cerr << "Closing Period is over" << std::endl; + } + + s->remove(h); + return; + } + if (ngtcp2_conn_is_in_draining_period(conn)) { + if (!config.quiet) { + std::cerr << "Draining Period is over" << std::endl; + } + + s->remove(h); + return; + } + + assert(0); +} +} // namespace + +namespace { +void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) { + int rv; + + auto h = static_cast<Handler *>(w->data); + auto s = h->server(); + + if (!config.quiet) { + std::cerr << "Timer expired" << std::endl; + } + + rv = h->handle_expiry(); + if (rv != 0) { + goto fail; + } + + rv = h->on_write(); + if (rv != 0) { + goto fail; + } + + return; + +fail: + switch (rv) { + case NETWORK_ERR_CLOSE_WAIT: + ev_timer_stop(loop, w); + return; + default: + s->remove(h); + return; + } +} +} // namespace + +Handler::Handler(struct ev_loop *loop, Server *server) + : loop_(loop), + server_(server), + qlog_(nullptr), + scid_{}, + httpconn_{nullptr}, + nkey_update_(0), + no_gso_{ +#ifdef UDP_SEGMENT + false +#else // !UDP_SEGMENT + true +#endif // !UDP_SEGMENT + }, + tx_{ + .data = std::unique_ptr<uint8_t[]>(new uint8_t[64_k]), + } { + ev_io_init(&wev_, writecb, 0, EV_WRITE); + wev_.data = this; + ev_timer_init(&timer_, timeoutcb, 0., 0.); + timer_.data = this; +} + +Handler::~Handler() { + if (!config.quiet) { + std::cerr << scid_ << " Closing QUIC connection " << std::endl; + } + + ev_timer_stop(loop_, &timer_); + ev_io_stop(loop_, &wev_); + + if (httpconn_) { + nghttp3_conn_del(httpconn_); + } + + if (qlog_) { + fclose(qlog_); + } +} + +namespace { +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + auto h = static_cast<Handler *>(user_data); + + if (!config.quiet) { + debug::handshake_completed(conn, user_data); + } + + if (h->handshake_completed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Handler::handshake_completed() { + if (!config.quiet) { + std::cerr << "Negotiated cipher suite is " << tls_session_.get_cipher_name() + << std::endl; + std::cerr << "Negotiated ALPN is " << tls_session_.get_selected_alpn() + << std::endl; + } + + if (tls_session_.send_session_ticket() != 0) { + std::cerr << "Unable to send session ticket" << std::endl; + } + + std::array<uint8_t, NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN> token; + + auto path = ngtcp2_conn_get_path(conn_); + auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + auto tokenlen = ngtcp2_crypto_generate_regular_token( + token.data(), config.static_secret.data(), config.static_secret.size(), + path->remote.addr, path->remote.addrlen, t); + if (tokenlen < 0) { + if (!config.quiet) { + std::cerr << "Unable to generate token" << std::endl; + } + return 0; + } + + if (auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen); + rv != 0) { + if (!config.quiet) { + std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv) + << std::endl; + } + return -1; + } + + return 0; +} + +namespace { +int do_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, const uint8_t *sample) { + if (ngtcp2_crypto_hp_mask(dest, hp, hp_ctx, sample) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (!config.quiet && config.show_secret) { + debug::print_hp_mask(dest, NGTCP2_HP_MASKLEN, sample, NGTCP2_HP_SAMPLELEN); + } + + return 0; +} +} // namespace + +namespace { +int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data) { + if (!config.quiet && !config.no_quic_dump) { + debug::print_crypto_data(crypto_level, data, datalen); + } + + return ngtcp2_crypto_recv_crypto_data_cb(conn, crypto_level, offset, data, + datalen, user_data); +} +} // namespace + +namespace { +int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t offset, const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + + if (h->recv_stream_data(flags, stream_id, data, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +int acked_stream_data_offset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t offset, uint64_t datalen, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->acked_stream_data_offset(stream_id, datalen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::acked_stream_data_offset(int64_t stream_id, uint64_t datalen) { + if (!httpconn_) { + return 0; + } + + if (auto rv = nghttp3_conn_add_ack_offset(httpconn_, stream_id, datalen); + rv != 0) { + std::cerr << "nghttp3_conn_add_ack_offset: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +namespace { +int stream_open(ngtcp2_conn *conn, int64_t stream_id, void *user_data) { + auto h = static_cast<Handler *>(user_data); + h->on_stream_open(stream_id); + return 0; +} +} // namespace + +void Handler::on_stream_open(int64_t stream_id) { + if (!ngtcp2_is_bidi_stream(stream_id)) { + return; + } + auto it = streams_.find(stream_id); + (void)it; + assert(it == std::end(streams_)); + streams_.emplace(stream_id, std::make_unique<Stream>(stream_id, this)); +} + +namespace { +int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + + if (!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + + if (h->on_stream_close(stream_id, app_error_code) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +namespace { +int stream_reset(ngtcp2_conn *conn, int64_t stream_id, uint64_t final_size, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->on_stream_reset(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::on_stream_reset(int64_t stream_id) { + if (httpconn_) { + if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); + rv != 0) { + std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + } + return 0; +} + +namespace { +int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->on_stream_stop_sending(stream_id) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::on_stream_stop_sending(int64_t stream_id) { + if (!httpconn_) { + return 0; + } + + if (auto rv = nghttp3_conn_shutdown_stream_read(httpconn_, stream_id); + rv != 0) { + std::cerr << "nghttp3_conn_shutdown_stream_read: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} + +namespace { +void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { + auto dis = std::uniform_int_distribution<uint8_t>(0, 255); + std::generate(dest, dest + destlen, [&dis]() { return dis(randgen); }); +} +} // namespace + +namespace { +int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token, + size_t cidlen, void *user_data) { + if (util::generate_secure_random(cid->data, cidlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + cid->datalen = cidlen; + if (ngtcp2_crypto_generate_stateless_reset_token( + token, config.static_secret.data(), config.static_secret.size(), + cid) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + auto h = static_cast<Handler *>(user_data); + h->server()->associate_cid(cid, h); + + return 0; +} +} // namespace + +namespace { +int remove_connection_id(ngtcp2_conn *conn, const ngtcp2_cid *cid, + void *user_data) { + auto h = static_cast<Handler *>(user_data); + h->server()->dissociate_cid(cid); + return 0; +} +} // namespace + +namespace { +int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen, + void *user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->update_key(rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx, + tx_iv, current_rx_secret, current_tx_secret, + secretlen) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +namespace { +int path_validation(ngtcp2_conn *conn, uint32_t flags, const ngtcp2_path *path, + ngtcp2_path_validation_result res, void *user_data) { + if (!config.quiet) { + debug::path_validation(path, res); + } + return 0; +} +} // namespace + +namespace { +int extend_max_remote_streams_bidi(ngtcp2_conn *conn, uint64_t max_streams, + void *user_data) { + auto h = static_cast<Handler *>(user_data); + h->extend_max_remote_streams_bidi(max_streams); + return 0; +} +} // namespace + +void Handler::extend_max_remote_streams_bidi(uint64_t max_streams) { + if (!httpconn_) { + return; + } + + nghttp3_conn_set_max_client_streams_bidi(httpconn_, max_streams); +} + +namespace { +int http_recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data, + size_t datalen, void *user_data, void *stream_user_data) { + if (!config.quiet && !config.no_http_dump) { + debug::print_http_data(stream_id, data, datalen); + } + auto h = static_cast<Handler *>(user_data); + h->http_consume(stream_id, datalen); + return 0; +} +} // namespace + +namespace { +int http_deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t nconsumed, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + h->http_consume(stream_id, nconsumed); + return 0; +} +} // namespace + +void Handler::http_consume(int64_t stream_id, size_t nconsumed) { + ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(conn_, nconsumed); +} + +namespace { +int http_begin_request_headers(nghttp3_conn *conn, int64_t stream_id, + void *user_data, void *stream_user_data) { + if (!config.quiet) { + debug::print_http_begin_request_headers(stream_id); + } + + auto h = static_cast<Handler *>(user_data); + h->http_begin_request_headers(stream_id); + return 0; +} +} // namespace + +void Handler::http_begin_request_headers(int64_t stream_id) { + auto it = streams_.find(stream_id); + assert(it != std::end(streams_)); + auto &stream = (*it).second; + + nghttp3_conn_set_stream_user_data(httpconn_, stream_id, stream.get()); +} + +namespace { +int http_recv_request_header(nghttp3_conn *conn, int64_t stream_id, + int32_t token, nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) { + if (!config.quiet) { + debug::print_http_header(stream_id, name, value, flags); + } + + auto h = static_cast<Handler *>(user_data); + auto stream = static_cast<Stream *>(stream_user_data); + h->http_recv_request_header(stream, token, name, value); + return 0; +} +} // namespace + +void Handler::http_recv_request_header(Stream *stream, int32_t token, + nghttp3_rcbuf *name, + nghttp3_rcbuf *value) { + auto v = nghttp3_rcbuf_get_buf(value); + + switch (token) { + case NGHTTP3_QPACK_TOKEN__PATH: + stream->uri = std::string{v.base, v.base + v.len}; + break; + case NGHTTP3_QPACK_TOKEN__METHOD: + stream->method = std::string{v.base, v.base + v.len}; + break; + case NGHTTP3_QPACK_TOKEN__AUTHORITY: + stream->authority = std::string{v.base, v.base + v.len}; + break; + } +} + +namespace { +int http_end_request_headers(nghttp3_conn *conn, int64_t stream_id, int fin, + void *user_data, void *stream_user_data) { + if (!config.quiet) { + debug::print_http_end_headers(stream_id); + } + + auto h = static_cast<Handler *>(user_data); + auto stream = static_cast<Stream *>(stream_user_data); + if (h->http_end_request_headers(stream) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::http_end_request_headers(Stream *stream) { + if (config.early_response) { + if (start_response(stream) != 0) { + return -1; + } + + shutdown_read(stream->stream_id, NGHTTP3_H3_NO_ERROR); + } + return 0; +} + +namespace { +int http_end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + auto stream = static_cast<Stream *>(stream_user_data); + if (h->http_end_stream(stream) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::http_end_stream(Stream *stream) { + if (!config.early_response) { + return start_response(stream); + } + return 0; +} + +int Handler::start_response(Stream *stream) { + return stream->start_response(httpconn_); +} + +namespace { +int http_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + auto stream = static_cast<Stream *>(stream_user_data); + h->http_acked_stream_data(stream, datalen); + return 0; +} +} // namespace + +void Handler::http_acked_stream_data(Stream *stream, uint64_t datalen) { + stream->http_acked_stream_data(datalen); + + if (stream->dynresp && stream->dynbuflen < MAX_DYNBUFLEN - 16_k) { + if (auto rv = nghttp3_conn_resume_stream(httpconn_, stream->stream_id); + rv != 0) { + // TODO Handle error + std::cerr << "nghttp3_conn_resume_stream: " << nghttp3_strerror(rv) + << std::endl; + } + } +} + +namespace { +int http_stream_close(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *conn_user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(conn_user_data); + h->http_stream_close(stream_id, app_error_code); + return 0; +} +} // namespace + +void Handler::http_stream_close(int64_t stream_id, uint64_t app_error_code) { + auto it = streams_.find(stream_id); + if (it == std::end(streams_)) { + return; + } + + if (!config.quiet) { + std::cerr << "HTTP stream " << stream_id << " closed with error code " + << app_error_code << std::endl; + } + + streams_.erase(it); + + if (ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(conn_, stream_id)); + ngtcp2_conn_extend_max_streams_bidi(conn_, 1); + } +} + +namespace { +int http_stop_sending(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->http_stop_sending(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::http_stop_sending(int64_t stream_id, uint64_t app_error_code) { + if (auto rv = + ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code); + rv != 0) { + std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + return 0; +} + +namespace { +int http_reset_stream(nghttp3_conn *conn, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->http_reset_stream(stream_id, app_error_code) != 0) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::http_reset_stream(int64_t stream_id, uint64_t app_error_code) { + if (auto rv = + ngtcp2_conn_shutdown_stream_write(conn_, stream_id, app_error_code); + rv != 0) { + std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + return 0; +} + +int Handler::setup_httpconn() { + if (httpconn_) { + return 0; + } + + if (ngtcp2_conn_get_max_local_streams_uni(conn_) < 3) { + std::cerr << "peer does not allow at least 3 unidirectional streams." + << std::endl; + return -1; + } + + nghttp3_callbacks callbacks{ + ::http_acked_stream_data, // acked_stream_data + ::http_stream_close, + ::http_recv_data, + ::http_deferred_consume, + ::http_begin_request_headers, + ::http_recv_request_header, + ::http_end_request_headers, + nullptr, // begin_trailers + nullptr, // recv_trailer + nullptr, // end_trailers + ::http_stop_sending, + ::http_end_stream, + ::http_reset_stream, + }; + nghttp3_settings settings; + nghttp3_settings_default(&settings); + settings.qpack_max_dtable_capacity = 4096; + settings.qpack_blocked_streams = 100; + + auto mem = nghttp3_mem_default(); + + if (auto rv = + nghttp3_conn_server_new(&httpconn_, &callbacks, &settings, mem, this); + rv != 0) { + std::cerr << "nghttp3_conn_server_new: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + auto params = ngtcp2_conn_get_local_transport_params(conn_); + + nghttp3_conn_set_max_client_streams_bidi(httpconn_, + params->initial_max_streams_bidi); + + int64_t ctrl_stream_id; + + if (auto rv = ngtcp2_conn_open_uni_stream(conn_, &ctrl_stream_id, nullptr); + rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + if (auto rv = nghttp3_conn_bind_control_stream(httpconn_, ctrl_stream_id); + rv != 0) { + std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + if (!config.quiet) { + fprintf(stderr, "http: control stream=%" PRIx64 "\n", ctrl_stream_id); + } + + int64_t qpack_enc_stream_id, qpack_dec_stream_id; + + if (auto rv = + ngtcp2_conn_open_uni_stream(conn_, &qpack_enc_stream_id, nullptr); + rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + if (auto rv = + ngtcp2_conn_open_uni_stream(conn_, &qpack_dec_stream_id, nullptr); + rv != 0) { + std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv) + << std::endl; + return -1; + } + + if (auto rv = nghttp3_conn_bind_qpack_streams(httpconn_, qpack_enc_stream_id, + qpack_dec_stream_id); + rv != 0) { + std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + if (!config.quiet) { + fprintf(stderr, + "http: QPACK streams encoder=%" PRIx64 " decoder=%" PRIx64 "\n", + qpack_enc_stream_id, qpack_dec_stream_id); + } + + return 0; +} + +namespace { +int extend_max_stream_data(ngtcp2_conn *conn, int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data) { + auto h = static_cast<Handler *>(user_data); + if (h->extend_max_stream_data(stream_id, max_data) != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + return 0; +} +} // namespace + +int Handler::extend_max_stream_data(int64_t stream_id, uint64_t max_data) { + if (auto rv = nghttp3_conn_unblock_stream(httpconn_, stream_id); rv != 0) { + std::cerr << "nghttp3_conn_unblock_stream: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + return 0; +} + +namespace { +int recv_tx_key(ngtcp2_conn *conn, ngtcp2_crypto_level level, void *user_data) { + if (level != NGTCP2_CRYPTO_LEVEL_APPLICATION) { + return 0; + } + + auto h = static_cast<Handler *>(user_data); + if (h->setup_httpconn() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +namespace { +void write_qlog(void *user_data, uint32_t flags, const void *data, + size_t datalen) { + auto h = static_cast<Handler *>(user_data); + h->write_qlog(data, datalen); +} +} // namespace + +void Handler::write_qlog(const void *data, size_t datalen) { + assert(qlog_); + fwrite(data, 1, datalen, qlog_); +} + +int Handler::init(const Endpoint &ep, const Address &local_addr, + const sockaddr *sa, socklen_t salen, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_cid *ocid, + const uint8_t *token, size_t tokenlen, uint32_t version, + TLSServerContext &tls_ctx) { + auto callbacks = ngtcp2_callbacks{ + nullptr, // client_initial + ngtcp2_crypto_recv_client_initial_cb, + ::recv_crypto_data, + ::handshake_completed, + nullptr, // recv_version_negotiation + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + do_hp_mask, + ::recv_stream_data, + ::acked_stream_data_offset, + stream_open, + stream_close, + nullptr, // recv_stateless_reset + nullptr, // recv_retry + nullptr, // extend_max_streams_bidi + nullptr, // extend_max_streams_uni + rand, + get_new_connection_id, + remove_connection_id, + ::update_key, + path_validation, + nullptr, // select_preferred_addr + ::stream_reset, + ::extend_max_remote_streams_bidi, + nullptr, // extend_max_remote_streams_uni + ::extend_max_stream_data, + nullptr, // dcid_status + nullptr, // handshake_confirmed + nullptr, // recv_new_token + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + nullptr, // recv_datagram + nullptr, // ack_datagram + nullptr, // lost_datagram + ngtcp2_crypto_get_path_challenge_data_cb, + stream_stop_sending, + ngtcp2_crypto_version_negotiation_cb, + nullptr, // recv_rx_key + ::recv_tx_key, + }; + + scid_.datalen = NGTCP2_SV_SCIDLEN; + if (util::generate_secure_random(scid_.data, scid_.datalen) != 0) { + std::cerr << "Could not generate connection ID" << std::endl; + return -1; + } + + ngtcp2_settings settings; + ngtcp2_settings_default(&settings); + settings.log_printf = config.quiet ? nullptr : debug::log_printf; + settings.initial_ts = util::timestamp(loop_); + settings.token = ngtcp2_vec{const_cast<uint8_t *>(token), tokenlen}; + settings.cc_algo = config.cc_algo; + settings.initial_rtt = config.initial_rtt; + settings.max_window = config.max_window; + settings.max_stream_window = config.max_stream_window; + settings.handshake_timeout = config.handshake_timeout; + settings.no_pmtud = config.no_pmtud; + settings.ack_thresh = config.ack_thresh; + if (config.max_udp_payload_size) { + settings.max_tx_udp_payload_size = config.max_udp_payload_size; + settings.no_tx_udp_payload_size_shaping = 1; + } + if (!config.qlog_dir.empty()) { + auto path = std::string{config.qlog_dir}; + path += '/'; + path += util::format_hex(scid_.data, scid_.datalen); + path += ".sqlog"; + qlog_ = fopen(path.c_str(), "w"); + if (qlog_ == nullptr) { + std::cerr << "Could not open qlog file " << std::quoted(path) << ": " + << strerror(errno) << std::endl; + return -1; + } + settings.qlog.write = ::write_qlog; + settings.qlog.odcid = *scid; + } + if (!config.preferred_versions.empty()) { + settings.preferred_versions = config.preferred_versions.data(); + settings.preferred_versionslen = config.preferred_versions.size(); + } + if (!config.other_versions.empty()) { + settings.other_versions = config.other_versions.data(); + settings.other_versionslen = config.other_versions.size(); + } + + ngtcp2_transport_params params; + ngtcp2_transport_params_default(¶ms); + params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; + params.initial_max_stream_data_bidi_remote = + config.max_stream_data_bidi_remote; + params.initial_max_stream_data_uni = config.max_stream_data_uni; + params.initial_max_data = config.max_data; + params.initial_max_streams_bidi = config.max_streams_bidi; + params.initial_max_streams_uni = config.max_streams_uni; + params.max_idle_timeout = config.timeout; + params.stateless_reset_token_present = 1; + params.active_connection_id_limit = 7; + + if (ocid) { + params.original_dcid = *ocid; + params.retry_scid = *scid; + params.retry_scid_present = 1; + } else { + params.original_dcid = *scid; + } + + if (util::generate_secure_random(params.stateless_reset_token, + sizeof(params.stateless_reset_token)) != 0) { + std::cerr << "Could not generate stateless reset token" << std::endl; + return -1; + } + + if (config.preferred_ipv4_addr.len || config.preferred_ipv6_addr.len) { + params.preferred_address_present = 1; + + if (config.preferred_ipv4_addr.len) { + params.preferred_address.ipv4 = config.preferred_ipv4_addr.su.in; + params.preferred_address.ipv4_present = 1; + } + + if (config.preferred_ipv6_addr.len) { + params.preferred_address.ipv6 = config.preferred_ipv6_addr.su.in6; + params.preferred_address.ipv6_present = 1; + } + + auto &token = params.preferred_address.stateless_reset_token; + if (util::generate_secure_random(token, sizeof(token)) != 0) { + std::cerr << "Could not generate preferred address stateless reset token" + << std::endl; + return -1; + } + + params.preferred_address.cid.datalen = NGTCP2_SV_SCIDLEN; + if (util::generate_secure_random(params.preferred_address.cid.data, + params.preferred_address.cid.datalen) != + 0) { + std::cerr << "Could not generate preferred address connection ID" + << std::endl; + return -1; + } + } + + auto path = ngtcp2_path{ + { + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }, + { + const_cast<sockaddr *>(sa), + salen, + }, + const_cast<Endpoint *>(&ep), + }; + if (auto rv = + ngtcp2_conn_server_new(&conn_, dcid, &scid_, &path, version, + &callbacks, &settings, ¶ms, nullptr, this); + rv != 0) { + std::cerr << "ngtcp2_conn_server_new: " << ngtcp2_strerror(rv) << std::endl; + return -1; + } + + if (tls_session_.init(tls_ctx, this) != 0) { + return -1; + } + + tls_session_.enable_keylog(); + + ngtcp2_conn_set_tls_native_handle(conn_, tls_session_.get_native_handle()); + + ev_io_set(&wev_, ep.fd, EV_WRITE); + + return 0; +} + +int Handler::feed_data(const Endpoint &ep, const Address &local_addr, + const sockaddr *sa, socklen_t salen, + const ngtcp2_pkt_info *pi, uint8_t *data, + size_t datalen) { + auto path = ngtcp2_path{ + { + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }, + { + const_cast<sockaddr *>(sa), + salen, + }, + const_cast<Endpoint *>(&ep), + }; + + if (auto rv = ngtcp2_conn_read_pkt(conn_, &path, pi, data, datalen, + util::timestamp(loop_)); + rv != 0) { + std::cerr << "ngtcp2_conn_read_pkt: " << ngtcp2_strerror(rv) << std::endl; + switch (rv) { + case NGTCP2_ERR_DRAINING: + start_draining_period(); + return NETWORK_ERR_CLOSE_WAIT; + case NGTCP2_ERR_RETRY: + return NETWORK_ERR_RETRY; + case NGTCP2_ERR_DROP_CONN: + return NETWORK_ERR_DROP_CONN; + case NGTCP2_ERR_CRYPTO: + if (!last_error_.error_code) { + ngtcp2_connection_close_error_set_transport_error_tls_alert( + &last_error_, ngtcp2_conn_get_tls_alert(conn_), nullptr, 0); + } + break; + default: + if (!last_error_.error_code) { + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, rv, nullptr, 0); + } + } + return handle_error(); + } + + return 0; +} + +int Handler::on_read(const Endpoint &ep, const Address &local_addr, + const sockaddr *sa, socklen_t salen, + const ngtcp2_pkt_info *pi, uint8_t *data, size_t datalen) { + if (auto rv = feed_data(ep, local_addr, sa, salen, pi, data, datalen); + rv != 0) { + return rv; + } + + update_timer(); + + return 0; +} + +int Handler::handle_expiry() { + auto now = util::timestamp(loop_); + if (auto rv = ngtcp2_conn_handle_expiry(conn_, now); rv != 0) { + std::cerr << "ngtcp2_conn_handle_expiry: " << ngtcp2_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_transport_error_liberr(&last_error_, rv, + nullptr, 0); + return handle_error(); + } + + return 0; +} + +int Handler::on_write() { + if (ngtcp2_conn_is_in_closing_period(conn_) || + ngtcp2_conn_is_in_draining_period(conn_)) { + return 0; + } + + if (tx_.send_blocked) { + if (auto rv = send_blocked_packet(); rv != 0) { + return rv; + } + + if (tx_.send_blocked) { + return 0; + } + } + + if (auto rv = write_streams(); rv != 0) { + return rv; + } + + update_timer(); + + return 0; +} + +int Handler::write_streams() { + std::array<nghttp3_vec, 16> vec; + ngtcp2_path_storage ps, prev_ps; + uint32_t prev_ecn = 0; + size_t pktcnt = 0; + auto max_udp_payload_size = ngtcp2_conn_get_max_tx_udp_payload_size(conn_); + auto path_max_udp_payload_size = + ngtcp2_conn_get_path_max_tx_udp_payload_size(conn_); + auto max_pktcnt = ngtcp2_conn_get_send_quantum(conn_) / max_udp_payload_size; + uint8_t *bufpos = tx_.data.get(); + ngtcp2_pkt_info pi; + size_t gso_size = 0; + auto ts = util::timestamp(loop_); + + ngtcp2_path_storage_zero(&ps); + ngtcp2_path_storage_zero(&prev_ps); + + max_pktcnt = std::min(max_pktcnt, static_cast<size_t>(config.max_gso_dgrams)); + + for (;;) { + int64_t stream_id = -1; + int fin = 0; + nghttp3_ssize sveccnt = 0; + + if (httpconn_ && ngtcp2_conn_get_max_data_left(conn_)) { + sveccnt = nghttp3_conn_writev_stream(httpconn_, &stream_id, &fin, + vec.data(), vec.size()); + if (sveccnt < 0) { + std::cerr << "nghttp3_conn_writev_stream: " << nghttp3_strerror(sveccnt) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(sveccnt), + nullptr, 0); + return handle_error(); + } + } + + ngtcp2_ssize ndatalen; + auto v = vec.data(); + auto vcnt = static_cast<size_t>(sveccnt); + + uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + if (fin) { + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + + auto nwrite = ngtcp2_conn_writev_stream( + conn_, &ps.path, &pi, bufpos, max_udp_payload_size, &ndatalen, flags, + stream_id, reinterpret_cast<const ngtcp2_vec *>(v), vcnt, ts); + if (nwrite < 0) { + switch (nwrite) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + assert(ndatalen == -1); + nghttp3_conn_block_stream(httpconn_, stream_id); + continue; + case NGTCP2_ERR_STREAM_SHUT_WR: + assert(ndatalen == -1); + nghttp3_conn_shutdown_stream_write(httpconn_, stream_id); + continue; + case NGTCP2_ERR_WRITE_MORE: + assert(ndatalen >= 0); + if (auto rv = + nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen); + rv != 0) { + std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, + 0); + return handle_error(); + } + continue; + } + + assert(ndatalen == -1); + + std::cerr << "ngtcp2_conn_writev_stream: " << ngtcp2_strerror(nwrite) + << std::endl; + ngtcp2_connection_close_error_set_transport_error_liberr( + &last_error_, nwrite, nullptr, 0); + return handle_error(); + } else if (ndatalen >= 0) { + if (auto rv = + nghttp3_conn_add_write_offset(httpconn_, stream_id, ndatalen); + rv != 0) { + std::cerr << "nghttp3_conn_add_write_offset: " << nghttp3_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, + 0); + return handle_error(); + } + } + + if (nwrite == 0) { + if (bufpos - tx_.data.get()) { + auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data; + + if (auto [nsent, rv] = server_->send_packet( + ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn, + data, datalen, gso_size); + rv != NETWORK_ERR_OK) { + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn, + data + nsent, datalen - nsent, gso_size); + + start_wev_endpoint(ep); + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + return 0; + } + } + + ev_io_stop(loop_, &wev_); + + // We are congestion limited. + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + return 0; + } + + bufpos += nwrite; + + if (pktcnt == 0) { + ngtcp2_path_copy(&prev_ps.path, &ps.path); + prev_ecn = pi.ecn; + gso_size = nwrite; + } else if (!ngtcp2_path_eq(&prev_ps.path, &ps.path) || prev_ecn != pi.ecn || + static_cast<size_t>(nwrite) > gso_size || + (gso_size > path_max_udp_payload_size && + static_cast<size_t>(nwrite) != gso_size)) { + auto &ep = *static_cast<Endpoint *>(prev_ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data - nwrite; + + if (auto [nsent, rv] = server_->send_packet( + ep, no_gso_, prev_ps.path.local, prev_ps.path.remote, prev_ecn, + data, datalen, gso_size); + rv != 0) { + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + on_send_blocked(ep, prev_ps.path.local, prev_ps.path.remote, prev_ecn, + data + nsent, datalen - nsent, gso_size); + + on_send_blocked(*static_cast<Endpoint *>(ps.path.user_data), + ps.path.local, ps.path.remote, pi.ecn, bufpos - nwrite, + nwrite, 0); + + start_wev_endpoint(ep); + } else { + auto &ep = *static_cast<Endpoint *>(ps.path.user_data); + auto data = bufpos - nwrite; + + if (auto [nsent, rv] = + server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote, + pi.ecn, data, nwrite, nwrite); + rv != 0) { + assert(nsent == 0); + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data, + nwrite, 0); + } + + start_wev_endpoint(ep); + } + + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + return 0; + } + + if (++pktcnt == max_pktcnt || static_cast<size_t>(nwrite) < gso_size) { + auto &ep = *static_cast<Endpoint *>(ps.path.user_data); + auto data = tx_.data.get(); + auto datalen = bufpos - data; + + if (auto [nsent, rv] = + server_->send_packet(ep, no_gso_, ps.path.local, ps.path.remote, + pi.ecn, data, datalen, gso_size); + rv != 0) { + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + on_send_blocked(ep, ps.path.local, ps.path.remote, pi.ecn, data + nsent, + datalen - nsent, gso_size); + } + + start_wev_endpoint(ep); + ngtcp2_conn_update_pkt_tx_time(conn_, ts); + return 0; + } + } +} + +void Handler::on_send_blocked(Endpoint &ep, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen, + size_t gso_size) { + assert(tx_.num_blocked || !tx_.send_blocked); + assert(tx_.num_blocked < 2); + + tx_.send_blocked = true; + + auto &p = tx_.blocked[tx_.num_blocked++]; + + memcpy(&p.local_addr.su, local_addr.addr, local_addr.addrlen); + memcpy(&p.remote_addr.su, remote_addr.addr, remote_addr.addrlen); + + p.local_addr.len = local_addr.addrlen; + p.remote_addr.len = remote_addr.addrlen; + p.endpoint = &ep; + p.ecn = ecn; + p.data = data; + p.datalen = datalen; + p.gso_size = gso_size; +} + +void Handler::start_wev_endpoint(const Endpoint &ep) { + // We do not close ep.fd, so we can expect that each Endpoint has + // unique fd. + if (ep.fd != wev_.fd) { + if (ev_is_active(&wev_)) { + ev_io_stop(loop_, &wev_); + } + + ev_io_set(&wev_, ep.fd, EV_WRITE); + } + + ev_io_start(loop_, &wev_); +} + +int Handler::send_blocked_packet() { + assert(tx_.send_blocked); + + for (; tx_.num_blocked_sent < tx_.num_blocked; ++tx_.num_blocked_sent) { + auto &p = tx_.blocked[tx_.num_blocked_sent]; + + ngtcp2_addr local_addr{ + .addr = &p.local_addr.su.sa, + .addrlen = p.local_addr.len, + }; + ngtcp2_addr remote_addr{ + .addr = &p.remote_addr.su.sa, + .addrlen = p.remote_addr.len, + }; + + auto [nsent, rv] = + server_->send_packet(*p.endpoint, no_gso_, local_addr, remote_addr, + p.ecn, p.data, p.datalen, p.gso_size); + if (rv != 0) { + assert(NETWORK_ERR_SEND_BLOCKED == rv); + + p.data += nsent; + p.datalen -= nsent; + + start_wev_endpoint(*p.endpoint); + + return 0; + } + } + + tx_.send_blocked = false; + tx_.num_blocked = 0; + tx_.num_blocked_sent = 0; + + return 0; +} + +void Handler::signal_write() { ev_io_start(loop_, &wev_); } + +void Handler::start_draining_period() { + ev_io_stop(loop_, &wev_); + + ev_set_cb(&timer_, close_waitcb); + timer_.repeat = + static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3; + ev_timer_again(loop_, &timer_); + + if (!config.quiet) { + std::cerr << "Draining period has started (" << timer_.repeat << " seconds)" + << std::endl; + } +} + +int Handler::start_closing_period() { + if (!conn_ || ngtcp2_conn_is_in_closing_period(conn_) || + ngtcp2_conn_is_in_draining_period(conn_)) { + return 0; + } + + ev_io_stop(loop_, &wev_); + + ev_set_cb(&timer_, close_waitcb); + timer_.repeat = + static_cast<ev_tstamp>(ngtcp2_conn_get_pto(conn_)) / NGTCP2_SECONDS * 3; + ev_timer_again(loop_, &timer_); + + if (!config.quiet) { + std::cerr << "Closing period has started (" << timer_.repeat << " seconds)" + << std::endl; + } + + conn_closebuf_ = std::make_unique<Buffer>(NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + ngtcp2_path_storage ps; + + ngtcp2_path_storage_zero(&ps); + + ngtcp2_pkt_info pi; + auto n = ngtcp2_conn_write_connection_close( + conn_, &ps.path, &pi, conn_closebuf_->wpos(), conn_closebuf_->left(), + &last_error_, util::timestamp(loop_)); + if (n < 0) { + std::cerr << "ngtcp2_conn_write_connection_close: " << ngtcp2_strerror(n) + << std::endl; + return -1; + } + + if (n == 0) { + return 0; + } + + conn_closebuf_->push(n); + + return 0; +} + +int Handler::handle_error() { + if (last_error_.type == + NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE) { + return -1; + } + + if (start_closing_period() != 0) { + return -1; + } + + if (ngtcp2_conn_is_in_draining_period(conn_)) { + return NETWORK_ERR_CLOSE_WAIT; + } + + if (auto rv = send_conn_close(); rv != NETWORK_ERR_OK) { + return rv; + } + + return NETWORK_ERR_CLOSE_WAIT; +} + +int Handler::send_conn_close() { + if (!config.quiet) { + std::cerr << "Closing Period: TX CONNECTION_CLOSE" << std::endl; + } + + assert(conn_closebuf_ && conn_closebuf_->size()); + assert(conn_); + assert(!ngtcp2_conn_is_in_draining_period(conn_)); + + auto path = ngtcp2_conn_get_path(conn_); + + return server_->send_packet( + *static_cast<Endpoint *>(path->user_data), path->local, path->remote, + /* ecn = */ 0, conn_closebuf_->rpos(), conn_closebuf_->size()); +} + +void Handler::update_timer() { + auto expiry = ngtcp2_conn_get_expiry(conn_); + auto now = util::timestamp(loop_); + + if (expiry <= now) { + if (!config.quiet) { + auto t = static_cast<ev_tstamp>(now - expiry) / NGTCP2_SECONDS; + std::cerr << "Timer has already expired: " << t << "s" << std::endl; + } + + ev_feed_event(loop_, &timer_, EV_TIMER); + + return; + } + + auto t = static_cast<ev_tstamp>(expiry - now) / NGTCP2_SECONDS; + if (!config.quiet) { + std::cerr << "Set timer=" << std::fixed << t << "s" << std::defaultfloat + << std::endl; + } + timer_.repeat = t; + ev_timer_again(loop_, &timer_); +} + +int Handler::recv_stream_data(uint32_t flags, int64_t stream_id, + const uint8_t *data, size_t datalen) { + if (!config.quiet && !config.no_quic_dump) { + debug::print_stream_data(stream_id, data, datalen); + } + + if (!httpconn_) { + return 0; + } + + auto nconsumed = nghttp3_conn_read_stream( + httpconn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN); + if (nconsumed < 0) { + std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr, + 0); + return -1; + } + + ngtcp2_conn_extend_max_stream_offset(conn_, stream_id, nconsumed); + ngtcp2_conn_extend_max_offset(conn_, nconsumed); + + return 0; +} + +int Handler::update_key(uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen) { + auto crypto_ctx = ngtcp2_conn_get_crypto_ctx(conn_); + auto aead = &crypto_ctx->aead; + auto keylen = ngtcp2_crypto_aead_keylen(aead); + auto ivlen = ngtcp2_crypto_packet_protection_ivlen(aead); + + ++nkey_update_; + + std::array<uint8_t, 64> rx_key, tx_key; + + if (ngtcp2_crypto_update_key(conn_, rx_secret, tx_secret, rx_aead_ctx, + rx_key.data(), rx_iv, tx_aead_ctx, tx_key.data(), + tx_iv, current_rx_secret, current_tx_secret, + secretlen) != 0) { + return -1; + } + + if (!config.quiet && config.show_secret) { + std::cerr << "application_traffic rx secret " << nkey_update_ << std::endl; + debug::print_secrets(rx_secret, secretlen, rx_key.data(), keylen, rx_iv, + ivlen); + std::cerr << "application_traffic tx secret " << nkey_update_ << std::endl; + debug::print_secrets(tx_secret, secretlen, tx_key.data(), keylen, tx_iv, + ivlen); + } + + return 0; +} + +Server *Handler::server() const { return server_; } + +int Handler::on_stream_close(int64_t stream_id, uint64_t app_error_code) { + if (!config.quiet) { + std::cerr << "QUIC stream " << stream_id << " closed" << std::endl; + } + + if (httpconn_) { + if (app_error_code == 0) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + auto rv = nghttp3_conn_close_stream(httpconn_, stream_id, app_error_code); + switch (rv) { + case 0: + break; + case NGHTTP3_ERR_STREAM_NOT_FOUND: + if (ngtcp2_is_bidi_stream(stream_id)) { + assert(!ngtcp2_conn_is_local_stream(conn_, stream_id)); + ngtcp2_conn_extend_max_streams_bidi(conn_, 1); + } + break; + default: + std::cerr << "nghttp3_conn_close_stream: " << nghttp3_strerror(rv) + << std::endl; + ngtcp2_connection_close_error_set_application_error( + &last_error_, nghttp3_err_infer_quic_app_error_code(rv), nullptr, 0); + return -1; + } + } + + return 0; +} + +void Handler::shutdown_read(int64_t stream_id, int app_error_code) { + ngtcp2_conn_shutdown_stream_read(conn_, stream_id, app_error_code); +} + +namespace { +void sreadcb(struct ev_loop *loop, ev_io *w, int revents) { + auto ep = static_cast<Endpoint *>(w->data); + + ep->server->on_read(*ep); +} +} // namespace + +namespace { +void siginthandler(struct ev_loop *loop, ev_signal *watcher, int revents) { + ev_break(loop, EVBREAK_ALL); +} +} // namespace + +Server::Server(struct ev_loop *loop, TLSServerContext &tls_ctx) + : loop_(loop), tls_ctx_(tls_ctx) { + ev_signal_init(&sigintev_, siginthandler, SIGINT); +} + +Server::~Server() { + disconnect(); + close(); +} + +void Server::disconnect() { + config.tx_loss_prob = 0; + + for (auto &ep : endpoints_) { + ev_io_stop(loop_, &ep.rev); + } + + ev_signal_stop(loop_, &sigintev_); + + while (!handlers_.empty()) { + auto it = std::begin(handlers_); + auto &h = (*it).second; + + h->handle_error(); + + remove(h); + } +} + +void Server::close() { + for (auto &ep : endpoints_) { + ::close(ep.fd); + } + + endpoints_.clear(); +} + +namespace { +int create_sock(Address &local_addr, const char *addr, const char *port, + int family) { + addrinfo hints{}; + addrinfo *res, *rp; + int val = 1; + + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + if (strcmp(addr, "*") == 0) { + addr = nullptr; + } + + if (auto rv = getaddrinfo(addr, port, &hints, &res); rv != 0) { + std::cerr << "getaddrinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + int fd = -1; + + for (rp = res; rp; rp = rp->ai_next) { + fd = util::create_nonblock_socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (fd == -1) { + continue; + } + + if (rp->ai_family == AF_INET6) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + continue; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + continue; + } + } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + continue; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + continue; + } + + fd_set_recv_ecn(fd, rp->ai_family); + fd_set_ip_mtu_discover(fd, rp->ai_family); + fd_set_ip_dontfrag(fd, family); + + if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + break; + } + + close(fd); + } + + if (!rp) { + std::cerr << "Could not bind" << std::endl; + return -1; + } + + socklen_t len = sizeof(local_addr.su.storage); + if (getsockname(fd, &local_addr.su.sa, &len) == -1) { + std::cerr << "getsockname: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + local_addr.len = len; + local_addr.ifindex = 0; + + return fd; +} + +} // namespace + +namespace { +int add_endpoint(std::vector<Endpoint> &endpoints, const char *addr, + const char *port, int af) { + Address dest; + auto fd = create_sock(dest, addr, port, af); + if (fd == -1) { + return -1; + } + + endpoints.emplace_back(); + auto &ep = endpoints.back(); + ep.addr = dest; + ep.fd = fd; + ev_io_init(&ep.rev, sreadcb, 0, EV_READ); + + return 0; +} +} // namespace + +namespace { +int add_endpoint(std::vector<Endpoint> &endpoints, const Address &addr) { + auto fd = util::create_nonblock_socket(addr.su.sa.sa_family, SOCK_DGRAM, 0); + if (fd == -1) { + std::cerr << "socket: " << strerror(errno) << std::endl; + return -1; + } + + int val = 1; + if (addr.su.sa.sa_family == AF_INET6) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + } else if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + close(fd); + return -1; + } + + fd_set_recv_ecn(fd, addr.su.sa.sa_family); + fd_set_ip_mtu_discover(fd, addr.su.sa.sa_family); + fd_set_ip_dontfrag(fd, addr.su.sa.sa_family); + + if (bind(fd, &addr.su.sa, addr.len) == -1) { + std::cerr << "bind: " << strerror(errno) << std::endl; + close(fd); + return -1; + } + + endpoints.emplace_back(Endpoint{}); + auto &ep = endpoints.back(); + ep.addr = addr; + ep.fd = fd; + ev_io_init(&ep.rev, sreadcb, 0, EV_READ); + + return 0; +} +} // namespace + +int Server::init(const char *addr, const char *port) { + endpoints_.reserve(4); + + auto ready = false; + if (!util::numeric_host(addr, AF_INET6) && + add_endpoint(endpoints_, addr, port, AF_INET) == 0) { + ready = true; + } + if (!util::numeric_host(addr, AF_INET) && + add_endpoint(endpoints_, addr, port, AF_INET6) == 0) { + ready = true; + } + if (!ready) { + return -1; + } + + if (config.preferred_ipv4_addr.len && + add_endpoint(endpoints_, config.preferred_ipv4_addr) != 0) { + return -1; + } + if (config.preferred_ipv6_addr.len && + add_endpoint(endpoints_, config.preferred_ipv6_addr) != 0) { + return -1; + } + + for (auto &ep : endpoints_) { + ep.server = this; + ep.rev.data = &ep; + + ev_io_set(&ep.rev, ep.fd, EV_READ); + + ev_io_start(loop_, &ep.rev); + } + + ev_signal_start(loop_, &sigintev_); + + return 0; +} + +int Server::on_read(Endpoint &ep) { + sockaddr_union su; + std::array<uint8_t, 64_k> buf; + ngtcp2_pkt_hd hd; + size_t pktcnt = 0; + ngtcp2_pkt_info pi; + + iovec msg_iov; + msg_iov.iov_base = buf.data(); + msg_iov.iov_len = buf.size(); + + msghdr msg{}; + msg.msg_name = &su; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t + msg_ctrl[CMSG_SPACE(sizeof(uint8_t)) + CMSG_SPACE(sizeof(in6_pktinfo))]; + msg.msg_control = msg_ctrl; + + for (; pktcnt < 10;) { + msg.msg_namelen = sizeof(su); + msg.msg_controllen = sizeof(msg_ctrl); + + auto nread = recvmsg(ep.fd, &msg, 0); + if (nread == -1) { + if (!(errno == EAGAIN || errno == ENOTCONN)) { + std::cerr << "recvmsg: " << strerror(errno) << std::endl; + } + return 0; + } + + ++pktcnt; + + pi.ecn = msghdr_get_ecn(&msg, su.storage.ss_family); + auto local_addr = msghdr_get_local_addr(&msg, su.storage.ss_family); + if (!local_addr) { + std::cerr << "Unable to obtain local address" << std::endl; + continue; + } + + set_port(*local_addr, ep.addr); + + if (!config.quiet) { + std::array<char, IF_NAMESIZE> ifname; + std::cerr << "Received packet: local=" + << util::straddr(&local_addr->su.sa, local_addr->len) + << " remote=" << util::straddr(&su.sa, msg.msg_namelen) + << " if=" << if_indextoname(local_addr->ifindex, ifname.data()) + << " ecn=0x" << std::hex << pi.ecn << std::dec << " " << nread + << " bytes" << std::endl; + } + + if (debug::packet_lost(config.rx_loss_prob)) { + if (!config.quiet) { + std::cerr << "** Simulated incoming packet loss **" << std::endl; + } + continue; + } + + if (nread == 0) { + continue; + } + + ngtcp2_version_cid vc; + + switch (auto rv = ngtcp2_pkt_decode_version_cid(&vc, buf.data(), nread, + NGTCP2_SV_SCIDLEN); + rv) { + case 0: + break; + case NGTCP2_ERR_VERSION_NEGOTIATION: + send_version_negotiation(vc.version, vc.scid, vc.scidlen, vc.dcid, + vc.dcidlen, ep, *local_addr, &su.sa, + msg.msg_namelen); + continue; + default: + std::cerr << "Could not decode version and CID from QUIC packet header: " + << ngtcp2_strerror(rv) << std::endl; + continue; + } + + auto dcid_key = util::make_cid_key(vc.dcid, vc.dcidlen); + + auto handler_it = handlers_.find(dcid_key); + if (handler_it == std::end(handlers_)) { + switch (auto rv = ngtcp2_accept(&hd, buf.data(), nread); rv) { + case 0: + break; + case NGTCP2_ERR_RETRY: + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3); + continue; + default: + if (!config.quiet) { + std::cerr << "Unexpected packet received: length=" << nread + << std::endl; + } + continue; + } + + ngtcp2_cid ocid; + ngtcp2_cid *pocid = nullptr; + + assert(hd.type == NGTCP2_PKT_INITIAL); + + if (config.validate_addr || hd.token.len) { + std::cerr << "Perform stateless address validation" << std::endl; + if (hd.token.len == 0) { + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3); + continue; + } + + if (hd.token.base[0] != NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY && + hd.dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN) { + send_stateless_connection_close(&hd, ep, *local_addr, &su.sa, + msg.msg_namelen); + continue; + } + + switch (hd.token.base[0]) { + case NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY: + if (verify_retry_token(&ocid, &hd, &su.sa, msg.msg_namelen) != 0) { + send_stateless_connection_close(&hd, ep, *local_addr, &su.sa, + msg.msg_namelen); + continue; + } + pocid = &ocid; + break; + case NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR: + if (verify_token(&hd, &su.sa, msg.msg_namelen) != 0) { + if (config.validate_addr) { + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, + nread * 3); + continue; + } + + hd.token.base = nullptr; + hd.token.len = 0; + } + break; + default: + if (!config.quiet) { + std::cerr << "Ignore unrecognized token" << std::endl; + } + if (config.validate_addr) { + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, + nread * 3); + continue; + } + + hd.token.base = nullptr; + hd.token.len = 0; + break; + } + } + + auto h = std::make_unique<Handler>(loop_, this); + if (h->init(ep, *local_addr, &su.sa, msg.msg_namelen, &hd.scid, &hd.dcid, + pocid, hd.token.base, hd.token.len, hd.version, + tls_ctx_) != 0) { + continue; + } + + switch (h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi, + buf.data(), nread)) { + case 0: + break; + case NETWORK_ERR_RETRY: + send_retry(&hd, ep, *local_addr, &su.sa, msg.msg_namelen, nread * 3); + continue; + default: + continue; + } + + switch (h->on_write()) { + case 0: + break; + default: + continue; + } + + std::array<ngtcp2_cid, 2> scids; + auto conn = h->conn(); + + auto num_scid = ngtcp2_conn_get_num_scid(conn); + + assert(num_scid <= scids.size()); + + ngtcp2_conn_get_scid(conn, scids.data()); + + for (size_t i = 0; i < num_scid; ++i) { + handlers_.emplace(util::make_cid_key(&scids[i]), h.get()); + } + + handlers_.emplace(dcid_key, h.get()); + + h.release(); + + continue; + } + + auto h = (*handler_it).second; + auto conn = h->conn(); + if (ngtcp2_conn_is_in_closing_period(conn)) { + // TODO do exponential backoff. + switch (h->send_conn_close()) { + case 0: + break; + default: + remove(h); + } + continue; + } + if (ngtcp2_conn_is_in_draining_period(conn)) { + continue; + } + + if (auto rv = h->on_read(ep, *local_addr, &su.sa, msg.msg_namelen, &pi, + buf.data(), nread); + rv != 0) { + if (rv != NETWORK_ERR_CLOSE_WAIT) { + remove(h); + } + continue; + } + + h->signal_write(); + } + + return 0; +} + +namespace { +uint32_t generate_reserved_version(const sockaddr *sa, socklen_t salen, + uint32_t version) { + uint32_t h = 0x811C9DC5u; + const uint8_t *p = (const uint8_t *)sa; + const uint8_t *ep = p + salen; + for (; p != ep; ++p) { + h ^= *p; + h *= 0x01000193u; + } + version = htonl(version); + p = (const uint8_t *)&version; + ep = p + sizeof(version); + for (; p != ep; ++p) { + h ^= *p; + h *= 0x01000193u; + } + h &= 0xf0f0f0f0u; + h |= 0x0a0a0a0au; + return h; +} +} // namespace + +int Server::send_version_negotiation(uint32_t version, const uint8_t *dcid, + size_t dcidlen, const uint8_t *scid, + size_t scidlen, Endpoint &ep, + const Address &local_addr, + const sockaddr *sa, socklen_t salen) { + Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE}; + std::array<uint32_t, 1 + max_preferred_versionslen> sv; + + auto p = std::begin(sv); + + *p++ = generate_reserved_version(sa, salen, version); + + if (config.preferred_versions.empty()) { + *p++ = NGTCP2_PROTO_VER_V1; + } else { + for (auto v : config.preferred_versions) { + *p++ = v; + } + } + + auto nwrite = ngtcp2_pkt_write_version_negotiation( + buf.wpos(), buf.left(), + std::uniform_int_distribution<uint8_t>( + 0, std::numeric_limits<uint8_t>::max())(randgen), + dcid, dcidlen, scid, scidlen, sv.data(), p - std::begin(sv)); + if (nwrite < 0) { + std::cerr << "ngtcp2_pkt_write_version_negotiation: " + << ngtcp2_strerror(nwrite) << std::endl; + return -1; + } + + buf.push(nwrite); + + ngtcp2_addr laddr{ + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }; + ngtcp2_addr raddr{ + const_cast<sockaddr *>(sa), + salen, + }; + + if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) != + NETWORK_ERR_OK) { + return -1; + } + + return 0; +} + +int Server::send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep, + const Address &local_addr, const sockaddr *sa, + socklen_t salen, size_t max_pktlen) { + std::array<char, NI_MAXHOST> host; + std::array<char, NI_MAXSERV> port; + + if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), + port.size(), NI_NUMERICHOST | NI_NUMERICSERV); + rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + if (!config.quiet) { + std::cerr << "Sending Retry packet to [" << host.data() + << "]:" << port.data() << std::endl; + } + + ngtcp2_cid scid; + + scid.datalen = NGTCP2_SV_SCIDLEN; + if (util::generate_secure_random(scid.data, scid.datalen) != 0) { + return -1; + } + + std::array<uint8_t, NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN> token; + + auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + auto tokenlen = ngtcp2_crypto_generate_retry_token( + token.data(), config.static_secret.data(), config.static_secret.size(), + chd->version, sa, salen, &scid, &chd->dcid, t); + if (tokenlen < 0) { + return -1; + } + + if (!config.quiet) { + std::cerr << "Generated address validation token:" << std::endl; + util::hexdump(stderr, token.data(), tokenlen); + } + + Buffer buf{ + std::min(static_cast<size_t>(NGTCP2_MAX_UDP_PAYLOAD_SIZE), max_pktlen)}; + + auto nwrite = ngtcp2_crypto_write_retry(buf.wpos(), buf.left(), chd->version, + &chd->scid, &scid, &chd->dcid, + token.data(), tokenlen); + if (nwrite < 0) { + std::cerr << "ngtcp2_crypto_write_retry failed" << std::endl; + return -1; + } + + buf.push(nwrite); + + ngtcp2_addr laddr{ + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }; + ngtcp2_addr raddr{ + const_cast<sockaddr *>(sa), + salen, + }; + + if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) != + NETWORK_ERR_OK) { + return -1; + } + + return 0; +} + +int Server::send_stateless_connection_close(const ngtcp2_pkt_hd *chd, + Endpoint &ep, + const Address &local_addr, + const sockaddr *sa, + socklen_t salen) { + Buffer buf{NGTCP2_MAX_UDP_PAYLOAD_SIZE}; + + auto nwrite = ngtcp2_crypto_write_connection_close( + buf.wpos(), buf.left(), chd->version, &chd->scid, &chd->dcid, + NGTCP2_INVALID_TOKEN, nullptr, 0); + if (nwrite < 0) { + std::cerr << "ngtcp2_crypto_write_connection_close failed" << std::endl; + return -1; + } + + buf.push(nwrite); + + ngtcp2_addr laddr{ + const_cast<sockaddr *>(&local_addr.su.sa), + local_addr.len, + }; + ngtcp2_addr raddr{ + const_cast<sockaddr *>(sa), + salen, + }; + + if (send_packet(ep, laddr, raddr, /* ecn = */ 0, buf.rpos(), buf.size()) != + NETWORK_ERR_OK) { + return -1; + } + + return 0; +} + +int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, + const sockaddr *sa, socklen_t salen) { + std::array<char, NI_MAXHOST> host; + std::array<char, NI_MAXSERV> port; + + if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), + port.size(), NI_NUMERICHOST | NI_NUMERICSERV); + rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + if (!config.quiet) { + std::cerr << "Verifying Retry token from [" << host.data() + << "]:" << port.data() << std::endl; + util::hexdump(stderr, hd->token.base, hd->token.len); + } + + auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + if (ngtcp2_crypto_verify_retry_token( + ocid, hd->token.base, hd->token.len, config.static_secret.data(), + config.static_secret.size(), hd->version, sa, salen, &hd->dcid, + 10 * NGTCP2_SECONDS, t) != 0) { + std::cerr << "Could not verify Retry token" << std::endl; + + return -1; + } + + if (!config.quiet) { + std::cerr << "Token was successfully validated" << std::endl; + } + + return 0; +} + +int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa, + socklen_t salen) { + std::array<char, NI_MAXHOST> host; + std::array<char, NI_MAXSERV> port; + + if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), + port.size(), NI_NUMERICHOST | NI_NUMERICSERV); + rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + if (!config.quiet) { + std::cerr << "Verifying token from [" << host.data() << "]:" << port.data() + << std::endl; + util::hexdump(stderr, hd->token.base, hd->token.len); + } + + auto t = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + if (ngtcp2_crypto_verify_regular_token(hd->token.base, hd->token.len, + config.static_secret.data(), + config.static_secret.size(), sa, salen, + 3600 * NGTCP2_SECONDS, t) != 0) { + if (!config.quiet) { + std::cerr << "Could not verify token" << std::endl; + } + return -1; + } + + if (!config.quiet) { + std::cerr << "Token was successfully validated" << std::endl; + } + + return 0; +} + +int Server::send_packet(Endpoint &ep, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen) { + auto no_gso = false; + auto [_, rv] = send_packet(ep, no_gso, local_addr, remote_addr, ecn, data, + datalen, datalen); + + return rv; +} + +std::pair<size_t, int> +Server::send_packet(Endpoint &ep, bool &no_gso, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen, size_t gso_size) { + assert(gso_size); + + if (debug::packet_lost(config.tx_loss_prob)) { + if (!config.quiet) { + std::cerr << "** Simulated outgoing packet loss **" << std::endl; + } + return {0, NETWORK_ERR_OK}; + } + + if (no_gso && datalen > gso_size) { + size_t nsent = 0; + + for (auto p = data; p < data + datalen; p += gso_size) { + auto len = std::min(gso_size, static_cast<size_t>(data + datalen - p)); + + auto [n, rv] = + send_packet(ep, no_gso, local_addr, remote_addr, ecn, p, len, len); + if (rv != 0) { + return {nsent, rv}; + } + + nsent += n; + } + + return {nsent, 0}; + } + + iovec msg_iov; + msg_iov.iov_base = const_cast<uint8_t *>(data); + msg_iov.iov_len = datalen; + + msghdr msg{}; + msg.msg_name = const_cast<sockaddr *>(remote_addr.addr); + msg.msg_namelen = remote_addr.addrlen; + msg.msg_iov = &msg_iov; + msg.msg_iovlen = 1; + + uint8_t + msg_ctrl[CMSG_SPACE(sizeof(uint16_t)) + CMSG_SPACE(sizeof(in6_pktinfo))]; + + memset(msg_ctrl, 0, sizeof(msg_ctrl)); + + msg.msg_control = msg_ctrl; + msg.msg_controllen = sizeof(msg_ctrl); + + size_t controllen = 0; + + auto cm = CMSG_FIRSTHDR(&msg); + + switch (local_addr.addr->sa_family) { + case AF_INET: { + controllen += CMSG_SPACE(sizeof(in_pktinfo)); + cm->cmsg_level = IPPROTO_IP; + cm->cmsg_type = IP_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); + auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cm)); + memset(pktinfo, 0, sizeof(in_pktinfo)); + auto addrin = reinterpret_cast<sockaddr_in *>(local_addr.addr); + pktinfo->ipi_spec_dst = addrin->sin_addr; + break; + } + case AF_INET6: { + controllen += CMSG_SPACE(sizeof(in6_pktinfo)); + cm->cmsg_level = IPPROTO_IPV6; + cm->cmsg_type = IPV6_PKTINFO; + cm->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); + auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cm)); + memset(pktinfo, 0, sizeof(in6_pktinfo)); + auto addrin = reinterpret_cast<sockaddr_in6 *>(local_addr.addr); + pktinfo->ipi6_addr = addrin->sin6_addr; + break; + } + default: + assert(0); + } + +#ifdef UDP_SEGMENT + if (datalen > gso_size) { + controllen += CMSG_SPACE(sizeof(uint16_t)); + cm = CMSG_NXTHDR(&msg, cm); + cm->cmsg_level = SOL_UDP; + cm->cmsg_type = UDP_SEGMENT; + cm->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + *(reinterpret_cast<uint16_t *>(CMSG_DATA(cm))) = gso_size; + } +#endif // UDP_SEGMENT + + msg.msg_controllen = controllen; + + if (ep.ecn != ecn) { + ep.ecn = ecn; + fd_set_ecn(ep.fd, ep.addr.su.storage.ss_family, ecn); + } + + ssize_t nwrite = 0; + + do { + nwrite = sendmsg(ep.fd, &msg, 0); + } while (nwrite == -1 && errno == EINTR); + + if (nwrite == -1) { + switch (errno) { + case EAGAIN: +#if EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif // EAGAIN != EWOULDBLOCK + return {0, NETWORK_ERR_SEND_BLOCKED}; +#ifdef UDP_SEGMENT + case EIO: + if (datalen > gso_size) { + // GSO failure; send each packet in a separate sendmsg call. + std::cerr << "sendmsg: disabling GSO due to " << strerror(errno) + << std::endl; + + no_gso = true; + + return send_packet(ep, no_gso, local_addr, remote_addr, ecn, data, + datalen, gso_size); + } + break; +#endif // UDP_SEGMENT + } + + std::cerr << "sendmsg: " << strerror(errno) << std::endl; + // TODO We have packet which is expected to fail to send (e.g., + // path validation to old path). + return {0, NETWORK_ERR_OK}; + } + + if (!config.quiet) { + std::cerr << "Sent packet: local=" + << util::straddr(local_addr.addr, local_addr.addrlen) + << " remote=" + << util::straddr(remote_addr.addr, remote_addr.addrlen) + << " ecn=0x" << std::hex << ecn << std::dec << " " << nwrite + << " bytes" << std::endl; + } + + return {nwrite, NETWORK_ERR_OK}; +} + +void Server::associate_cid(const ngtcp2_cid *cid, Handler *h) { + handlers_.emplace(util::make_cid_key(cid), h); +} + +void Server::dissociate_cid(const ngtcp2_cid *cid) { + handlers_.erase(util::make_cid_key(cid)); +} + +void Server::remove(const Handler *h) { + auto conn = h->conn(); + + handlers_.erase( + util::make_cid_key(ngtcp2_conn_get_client_initial_dcid(conn))); + + std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(conn)); + ngtcp2_conn_get_scid(conn, cids.data()); + + for (auto &cid : cids) { + handlers_.erase(util::make_cid_key(&cid)); + } + + delete h; +} + +namespace { +int parse_host_port(Address &dest, int af, const char *first, + const char *last) { + if (std::distance(first, last) == 0) { + return -1; + } + + const char *host_begin, *host_end, *it; + if (*first == '[') { + host_begin = first + 1; + it = std::find(host_begin, last, ']'); + if (it == last) { + return -1; + } + host_end = it; + ++it; + if (it == last || *it != ':') { + return -1; + } + } else { + host_begin = first; + it = std::find(host_begin, last, ':'); + if (it == last) { + return -1; + } + host_end = it; + } + + if (++it == last) { + return -1; + } + auto svc_begin = it; + + std::array<char, NI_MAXHOST> host; + *std::copy(host_begin, host_end, std::begin(host)) = '\0'; + + addrinfo hints{}, *res; + hints.ai_family = af; + hints.ai_socktype = SOCK_DGRAM; + + if (auto rv = getaddrinfo(host.data(), svc_begin, &hints, &res); rv != 0) { + std::cerr << "getaddrinfo: [" << host.data() << "]:" << svc_begin << ": " + << gai_strerror(rv) << std::endl; + return -1; + } + + dest.len = res->ai_addrlen; + memcpy(&dest.su, res->ai_addr, res->ai_addrlen); + + freeaddrinfo(res); + + return 0; +} +} // namespace + +namespace { +void print_usage() { + std::cerr << "Usage: server [OPTIONS] <ADDR> <PORT> <PRIVATE_KEY_FILE> " + "<CERTIFICATE_FILE>" + << std::endl; +} +} // namespace + +namespace { +void config_set_default(Config &config) { + config = Config{}; + config.tx_loss_prob = 0.; + config.rx_loss_prob = 0.; + config.ciphers = util::crypto_default_ciphers(); + config.groups = util::crypto_default_groups(); + config.timeout = 30 * NGTCP2_SECONDS; + { + auto path = realpath(".", nullptr); + assert(path); + config.htdocs = path; + free(path); + } + config.mime_types_file = "/etc/mime.types"sv; + config.max_data = 1_m; + config.max_stream_data_bidi_local = 256_k; + config.max_stream_data_bidi_remote = 256_k; + config.max_stream_data_uni = 256_k; + config.max_window = 6_m; + config.max_stream_window = 6_m; + config.max_streams_bidi = 100; + config.max_streams_uni = 3; + config.max_dyn_length = 20_m; + config.cc_algo = NGTCP2_CC_ALGO_CUBIC; + config.initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; + config.max_gso_dgrams = 64; + config.handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT; + config.ack_thresh = 2; +} +} // namespace + +namespace { +void print_help() { + print_usage(); + + config_set_default(config); + + std::cout << R"( + <ADDR> Address to listen to. '*' binds to any address. + <PORT> Port + <PRIVATE_KEY_FILE> + Path to private key file + <CERTIFICATE_FILE> + Path to certificate file +Options: + -t, --tx-loss=<P> + The probability of losing outgoing packets. <P> must be + [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 + means 100% packet loss. + -r, --rx-loss=<P> + The probability of losing incoming packets. <P> must be + [0.0, 1.0], inclusive. 0.0 means no packet loss. 1.0 + means 100% packet loss. + --ciphers=<CIPHERS> + Specify the cipher suite list to enable. + Default: )" + << config.ciphers << R"( + --groups=<GROUPS> + Specify the supported groups. + Default: )" + << config.groups << R"( + -d, --htdocs=<PATH> + Specify document root. If this option is not specified, + the document root is the current working directory. + -q, --quiet Suppress debug output. + -s, --show-secret + Print out secrets unless --quiet is used. + --timeout=<DURATION> + Specify idle timeout. + Default: )" + << util::format_duration(config.timeout) << R"( + -V, --validate-addr + Perform address validation. + --preferred-ipv4-addr=<ADDR>:<PORT> + Specify preferred IPv4 address and port. + --preferred-ipv6-addr=<ADDR>:<PORT> + Specify preferred IPv6 address and port. A numeric IPv6 + address must be enclosed by '[' and ']' (e.g., + [::1]:8443) + --mime-types-file=<PATH> + Path to file that contains MIME media types and the + extensions. + Default: )" + << config.mime_types_file << R"( + --early-response + Start sending response when it receives HTTP header + fields without waiting for request body. If HTTP + response data is written before receiving request body, + STOP_SENDING is sent. + --verify-client + Request a client certificate. At the moment, we just + request a certificate and no verification is done. + --qlog-dir=<PATH> + Path to the directory where qlog file is stored. The + file name of each qlog is the Source Connection ID of + server. + --no-quic-dump + Disables printing QUIC STREAM and CRYPTO frame data out. + --no-http-dump + Disables printing HTTP response body out. + --max-data=<SIZE> + The initial connection-level flow control window. + Default: )" + << util::format_uint_iec(config.max_data) << R"( + --max-stream-data-bidi-local=<SIZE> + The initial stream-level flow control window for a + bidirectional stream that the local endpoint initiates. + Default: )" + << util::format_uint_iec(config.max_stream_data_bidi_local) << R"( + --max-stream-data-bidi-remote=<SIZE> + The initial stream-level flow control window for a + bidirectional stream that the remote endpoint initiates. + Default: )" + << util::format_uint_iec(config.max_stream_data_bidi_remote) << R"( + --max-stream-data-uni=<SIZE> + The initial stream-level flow control window for a + unidirectional stream. + Default: )" + << util::format_uint_iec(config.max_stream_data_uni) << R"( + --max-streams-bidi=<N> + The number of the concurrent bidirectional streams. + Default: )" + << config.max_streams_bidi << R"( + --max-streams-uni=<N> + The number of the concurrent unidirectional streams. + Default: )" + << config.max_streams_uni << R"( + --max-dyn-length=<SIZE> + The maximum length of a dynamically generated content. + Default: )" + << util::format_uint_iec(config.max_dyn_length) << R"( + --cc=(cubic|reno|bbr|bbr2) + The name of congestion controller algorithm. + Default: )" + << util::strccalgo(config.cc_algo) << R"( + --initial-rtt=<DURATION> + Set an initial RTT. + Default: )" + << util::format_duration(config.initial_rtt) << R"( + --max-udp-payload-size=<SIZE> + Override maximum UDP payload size that server transmits. + --send-trailers + Send trailer fields. + --max-window=<SIZE> + Maximum connection-level flow control window size. The + window auto-tuning is enabled if nonzero value is given, + and window size is scaled up to this value. + Default: )" + << util::format_uint_iec(config.max_window) << R"( + --max-stream-window=<SIZE> + Maximum stream-level flow control window size. The + window auto-tuning is enabled if nonzero value is given, + and window size is scaled up to this value. + Default: )" + << util::format_uint_iec(config.max_stream_window) << R"( + --max-gso-dgrams=<N> + Maximum number of UDP datagrams that are sent in a + single GSO sendmsg call. + Default: )" + << config.max_gso_dgrams << R"( + --handshake-timeout=<DURATION> + Set the QUIC handshake timeout. + Default: )" + << util::format_duration(config.handshake_timeout) << R"( + --preferred-versions=<HEX>[[,<HEX>]...] + Specify QUIC versions in hex string in the order of + preference. Server negotiates one of those versions if + client initially selects a less preferred version. + These versions must be supported by libngtcp2. Instead + of specifying hex string, there are special aliases + available: "v1" indicates QUIC v1, and "v2draft" + indicates QUIC v2 draft. + --other-versions=<HEX>[[,<HEX>]...] + Specify QUIC versions in hex string that are sent in + other_versions field of version_information transport + parameter. This list can include a version which is not + supported by libngtcp2. Instead of specifying hex + string, there are special aliases available: "v1" + indicates QUIC v1, and "v2draft" indicates QUIC v2 + draft. + --no-pmtud Disables Path MTU Discovery. + --ack-thresh=<N> + The minimum number of the received ACK eliciting packets + that triggers immediate acknowledgement. + Default: )" + << config.ack_thresh << R"( + -h, --help Display this help and exit. + +--- + + The <SIZE> argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The <DURATION> argument is an integer and an optional unit (e.g., 1s + is 1 second and 500ms is 500 milliseconds). Units are h, m, s, ms, + us, or ns (hours, minutes, seconds, milliseconds, microseconds, and + nanoseconds respectively). If a unit is omitted, a second is used + as unit. + + The <HEX> argument is an hex string which must start with "0x" + (e.g., 0x00000001).)" + << std::endl; +} +} // namespace + +std::ofstream keylog_file; + +int main(int argc, char **argv) { + config_set_default(config); + + for (;;) { + static int flag = 0; + constexpr static option long_opts[] = { + {"help", no_argument, nullptr, 'h'}, + {"tx-loss", required_argument, nullptr, 't'}, + {"rx-loss", required_argument, nullptr, 'r'}, + {"htdocs", required_argument, nullptr, 'd'}, + {"quiet", no_argument, nullptr, 'q'}, + {"show-secret", no_argument, nullptr, 's'}, + {"validate-addr", no_argument, nullptr, 'V'}, + {"ciphers", required_argument, &flag, 1}, + {"groups", required_argument, &flag, 2}, + {"timeout", required_argument, &flag, 3}, + {"preferred-ipv4-addr", required_argument, &flag, 4}, + {"preferred-ipv6-addr", required_argument, &flag, 5}, + {"mime-types-file", required_argument, &flag, 6}, + {"early-response", no_argument, &flag, 7}, + {"verify-client", no_argument, &flag, 8}, + {"qlog-dir", required_argument, &flag, 9}, + {"no-quic-dump", no_argument, &flag, 10}, + {"no-http-dump", no_argument, &flag, 11}, + {"max-data", required_argument, &flag, 12}, + {"max-stream-data-bidi-local", required_argument, &flag, 13}, + {"max-stream-data-bidi-remote", required_argument, &flag, 14}, + {"max-stream-data-uni", required_argument, &flag, 15}, + {"max-streams-bidi", required_argument, &flag, 16}, + {"max-streams-uni", required_argument, &flag, 17}, + {"max-dyn-length", required_argument, &flag, 18}, + {"cc", required_argument, &flag, 19}, + {"initial-rtt", required_argument, &flag, 20}, + {"max-udp-payload-size", required_argument, &flag, 21}, + {"send-trailers", no_argument, &flag, 22}, + {"max-window", required_argument, &flag, 23}, + {"max-stream-window", required_argument, &flag, 24}, + {"max-gso-dgrams", required_argument, &flag, 25}, + {"handshake-timeout", required_argument, &flag, 26}, + {"preferred-versions", required_argument, &flag, 27}, + {"other-versions", required_argument, &flag, 28}, + {"no-pmtud", no_argument, &flag, 29}, + {"ack-thresh", required_argument, &flag, 30}, + {nullptr, 0, nullptr, 0}}; + + auto optidx = 0; + auto c = getopt_long(argc, argv, "d:hqr:st:V", long_opts, &optidx); + if (c == -1) { + break; + } + switch (c) { + case 'd': { + // --htdocs + auto path = realpath(optarg, nullptr); + if (path == nullptr) { + std::cerr << "path: invalid path " << std::quoted(optarg) << std::endl; + exit(EXIT_FAILURE); + } + config.htdocs = path; + free(path); + break; + } + case 'h': + // --help + print_help(); + exit(EXIT_SUCCESS); + case 'q': + // --quiet + config.quiet = true; + break; + case 'r': + // --rx-loss + config.rx_loss_prob = strtod(optarg, nullptr); + break; + case 's': + // --show-secret + config.show_secret = true; + break; + case 't': + // --tx-loss + config.tx_loss_prob = strtod(optarg, nullptr); + break; + case 'V': + // --validate-addr + config.validate_addr = true; + break; + case '?': + print_usage(); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // --ciphers + config.ciphers = optarg; + break; + case 2: + // --groups + config.groups = optarg; + break; + case 3: + // --timeout + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "timeout: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.timeout = *t; + } + break; + case 4: + // --preferred-ipv4-addr + if (parse_host_port(config.preferred_ipv4_addr, AF_INET, optarg, + optarg + strlen(optarg)) != 0) { + std::cerr << "preferred-ipv4-addr: could not use " + << std::quoted(optarg) << std::endl; + exit(EXIT_FAILURE); + } + break; + case 5: + // --preferred-ipv6-addr + if (parse_host_port(config.preferred_ipv6_addr, AF_INET6, optarg, + optarg + strlen(optarg)) != 0) { + std::cerr << "preferred-ipv6-addr: could not use " + << std::quoted(optarg) << std::endl; + exit(EXIT_FAILURE); + } + break; + case 6: + // --mime-types-file + config.mime_types_file = optarg; + break; + case 7: + // --early-response + config.early_response = true; + break; + case 8: + // --verify-client + config.verify_client = true; + break; + case 9: + // --qlog-dir + config.qlog_dir = optarg; + break; + case 10: + // --no-quic-dump + config.no_quic_dump = true; + break; + case 11: + // --no-http-dump + config.no_http_dump = true; + break; + case 12: + // --max-data + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-data: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_data = *n; + } + break; + case 13: + // --max-stream-data-bidi-local + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-bidi-local: invalid argument" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_bidi_local = *n; + } + break; + case 14: + // --max-stream-data-bidi-remote + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-bidi-remote: invalid argument" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_bidi_remote = *n; + } + break; + case 15: + // --max-stream-data-uni + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-data-uni: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_data_uni = *n; + } + break; + case 16: + // --max-streams-bidi + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-streams-bidi: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_streams_bidi = *n; + } + break; + case 17: + // --max-streams-uni + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-streams-uni: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_streams_uni = *n; + } + break; + case 18: + // --max-dyn-length + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-dyn-length: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_dyn_length = *n; + } + break; + case 19: + // --cc + if (strcmp("cubic", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_CUBIC; + break; + } + if (strcmp("reno", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_RENO; + break; + } + if (strcmp("bbr", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_BBR; + break; + } + if (strcmp("bbr2", optarg) == 0) { + config.cc_algo = NGTCP2_CC_ALGO_BBR2; + break; + } + std::cerr << "cc: specify cubic, reno, bbr, or bbr2" << std::endl; + exit(EXIT_FAILURE); + case 20: + // --initial-rtt + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "initial-rtt: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.initial_rtt = *t; + } + break; + case 21: + // --max-udp-payload-size + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-udp-payload-size: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 64_k) { + std::cerr << "max-udp-payload-size: must not exceed 65536" + << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_udp_payload_size = *n; + } + break; + case 22: + // --send-trailers + config.send_trailers = true; + break; + case 23: + // --max-window + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-window: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_window = *n; + } + break; + case 24: + // --max-stream-window + if (auto n = util::parse_uint_iec(optarg); !n) { + std::cerr << "max-stream-window: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_stream_window = *n; + } + break; + case 25: + // --max-gso-dgrams + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "max-gso-dgrams: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.max_gso_dgrams = *n; + } + break; + case 26: + // --handshake-timeout + if (auto t = util::parse_duration(optarg); !t) { + std::cerr << "handshake-timeout: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else { + config.handshake_timeout = *t; + } + break; + case 27: { + // --preferred-versions + auto l = util::split_str(optarg); + if (l.size() > max_preferred_versionslen) { + std::cerr << "preferred-versions: too many versions > " + << max_preferred_versionslen << std::endl; + } + config.preferred_versions.resize(l.size()); + auto it = std::begin(config.preferred_versions); + for (const auto &k : l) { + if (k == "v1"sv) { + *it++ = NGTCP2_PROTO_VER_V1; + continue; + } + if (k == "v2draft"sv) { + *it++ = NGTCP2_PROTO_VER_V2_DRAFT; + continue; + } + auto rv = util::parse_version(k); + if (!rv) { + std::cerr << "preferred-versions: invalid version " + << std::quoted(k) << std::endl; + exit(EXIT_FAILURE); + } + if (!ngtcp2_is_supported_version(*rv)) { + std::cerr << "preferred-versions: unsupported version " + << std::quoted(k) << std::endl; + exit(EXIT_FAILURE); + } + *it++ = *rv; + } + break; + } + case 28: { + // --other-versions + auto l = util::split_str(optarg); + config.other_versions.resize(l.size()); + auto it = std::begin(config.other_versions); + for (const auto &k : l) { + if (k == "v1"sv) { + *it++ = NGTCP2_PROTO_VER_V1; + continue; + } + if (k == "v2draft"sv) { + *it++ = NGTCP2_PROTO_VER_V2_DRAFT; + continue; + } + auto rv = util::parse_version(k); + if (!rv) { + std::cerr << "other-versions: invalid version " << std::quoted(k) + << std::endl; + exit(EXIT_FAILURE); + } + *it++ = *rv; + } + break; + } + case 29: + // --no-pmtud + config.no_pmtud = true; + break; + case 30: + // --ack-thresh + if (auto n = util::parse_uint(optarg); !n) { + std::cerr << "ack-thresh: invalid argument" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 100) { + std::cerr << "ack-thresh: must not exceed 100" << std::endl; + exit(EXIT_FAILURE); + } else { + config.ack_thresh = *n; + } + break; + } + break; + default: + break; + }; + } + + if (argc - optind < 4) { + std::cerr << "Too few arguments" << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + auto addr = argv[optind++]; + auto port = argv[optind++]; + auto private_key_file = argv[optind++]; + auto cert_file = argv[optind++]; + + if (auto n = util::parse_uint(port); !n) { + std::cerr << "port: invalid port number" << std::endl; + exit(EXIT_FAILURE); + } else if (*n > 65535) { + std::cerr << "port: must not exceed 65535" << std::endl; + exit(EXIT_FAILURE); + } else { + config.port = *n; + } + + if (auto mt = util::read_mime_types(config.mime_types_file); !mt) { + std::cerr << "mime-types-file: Could not read MIME media types file " + << std::quoted(config.mime_types_file) << std::endl; + } else { + config.mime_types = std::move(*mt); + } + + TLSServerContext tls_ctx; + + if (tls_ctx.init(private_key_file, cert_file, AppProtocol::H3) != 0) { + exit(EXIT_FAILURE); + } + + if (config.htdocs.back() != '/') { + config.htdocs += '/'; + } + + std::cerr << "Using document root " << config.htdocs << std::endl; + + auto ev_loop_d = defer(ev_loop_destroy, EV_DEFAULT); + + auto keylog_filename = getenv("SSLKEYLOGFILE"); + if (keylog_filename) { + keylog_file.open(keylog_filename, std::ios_base::app); + if (keylog_file) { + tls_ctx.enable_keylog(); + } + } + + if (util::generate_secret(config.static_secret.data(), + config.static_secret.size()) != 0) { + std::cerr << "Unable to generate static secret" << std::endl; + exit(EXIT_FAILURE); + } + + Server s(EV_DEFAULT, tls_ctx); + if (s.init(addr, port) != 0) { + exit(EXIT_FAILURE); + } + + ev_run(EV_DEFAULT, 0); + + s.disconnect(); + s.close(); + + return EXIT_SUCCESS; +} diff --git a/examples/server.h b/examples/server.h new file mode 100644 index 0000000..51fdfd0 --- /dev/null +++ b/examples/server.h @@ -0,0 +1,256 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SERVER_H +#define SERVER_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <vector> +#include <unordered_map> +#include <string> +#include <deque> +#include <string_view> +#include <memory> + +#include <ngtcp2/ngtcp2.h> +#include <ngtcp2/ngtcp2_crypto.h> +#include <nghttp3/nghttp3.h> + +#include <ev.h> + +#include "server_base.h" +#include "tls_server_context.h" +#include "network.h" +#include "shared.h" + +using namespace ngtcp2; + +struct HTTPHeader { + HTTPHeader(const std::string_view &name, const std::string_view &value) + : name(name), value(value) {} + + std::string_view name; + std::string_view value; +}; + +class Handler; +struct FileEntry; + +struct Stream { + Stream(int64_t stream_id, Handler *handler); + + int start_response(nghttp3_conn *conn); + std::pair<FileEntry, int> open_file(const std::string &path); + void map_file(const FileEntry &fe); + int send_status_response(nghttp3_conn *conn, unsigned int status_code, + const std::vector<HTTPHeader> &extra_headers = {}); + int send_redirect_response(nghttp3_conn *conn, unsigned int status_code, + const std::string_view &path); + int64_t find_dyn_length(const std::string_view &path); + void http_acked_stream_data(uint64_t datalen); + + int64_t stream_id; + Handler *handler; + // uri is request uri/path. + std::string uri; + std::string method; + std::string authority; + std::string status_resp_body; + // data is a pointer to the memory which maps file denoted by fd. + uint8_t *data; + // datalen is the length of mapped file by data. + uint64_t datalen; + // dynresp is true if dynamic data response is enabled. + bool dynresp; + // dyndataleft is the number of dynamic data left to send. + uint64_t dyndataleft; + // dynbuflen is the number of bytes in-flight. + uint64_t dynbuflen; +}; + +class Server; + +// Endpoint is a local endpoint. +struct Endpoint { + Address addr; + ev_io rev; + Server *server; + int fd; + // ecn is the last ECN bits set to fd. + unsigned int ecn; +}; + +class Handler : public HandlerBase { +public: + Handler(struct ev_loop *loop, Server *server); + ~Handler(); + + int init(const Endpoint &ep, const Address &local_addr, const sockaddr *sa, + socklen_t salen, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_cid *ocid, const uint8_t *token, size_t tokenlen, + uint32_t version, TLSServerContext &tls_ctx); + + int on_read(const Endpoint &ep, const Address &local_addr, const sockaddr *sa, + socklen_t salen, const ngtcp2_pkt_info *pi, uint8_t *data, + size_t datalen); + int on_write(); + int write_streams(); + int feed_data(const Endpoint &ep, const Address &local_addr, + const sockaddr *sa, socklen_t salen, const ngtcp2_pkt_info *pi, + uint8_t *data, size_t datalen); + void update_timer(); + int handle_expiry(); + void signal_write(); + int handshake_completed(); + + Server *server() const; + int recv_stream_data(uint32_t flags, int64_t stream_id, const uint8_t *data, + size_t datalen); + int acked_stream_data_offset(int64_t stream_id, uint64_t datalen); + uint32_t version() const; + void on_stream_open(int64_t stream_id); + int on_stream_close(int64_t stream_id, uint64_t app_error_code); + void start_draining_period(); + int start_closing_period(); + int handle_error(); + int send_conn_close(); + + int update_key(uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen); + + int setup_httpconn(); + void http_consume(int64_t stream_id, size_t nconsumed); + void extend_max_remote_streams_bidi(uint64_t max_streams); + Stream *find_stream(int64_t stream_id); + void http_begin_request_headers(int64_t stream_id); + void http_recv_request_header(Stream *stream, int32_t token, + nghttp3_rcbuf *name, nghttp3_rcbuf *value); + int http_end_request_headers(Stream *stream); + int http_end_stream(Stream *stream); + int start_response(Stream *stream); + int on_stream_reset(int64_t stream_id); + int on_stream_stop_sending(int64_t stream_id); + int extend_max_stream_data(int64_t stream_id, uint64_t max_data); + void shutdown_read(int64_t stream_id, int app_error_code); + void http_acked_stream_data(Stream *stream, uint64_t datalen); + void http_stream_close(int64_t stream_id, uint64_t app_error_code); + int http_stop_sending(int64_t stream_id, uint64_t app_error_code); + int http_reset_stream(int64_t stream_id, uint64_t app_error_code); + + void write_qlog(const void *data, size_t datalen); + + void on_send_blocked(Endpoint &ep, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen, size_t gso_size); + void start_wev_endpoint(const Endpoint &ep); + int send_blocked_packet(); + +private: + struct ev_loop *loop_; + Server *server_; + ev_io wev_; + ev_timer timer_; + FILE *qlog_; + ngtcp2_cid scid_; + nghttp3_conn *httpconn_; + std::unordered_map<int64_t, std::unique_ptr<Stream>> streams_; + // conn_closebuf_ contains a packet which contains CONNECTION_CLOSE. + // This packet is repeatedly sent as a response to the incoming + // packet in draining period. + std::unique_ptr<Buffer> conn_closebuf_; + // nkey_update_ is the number of key update occurred. + size_t nkey_update_; + bool no_gso_; + + struct { + bool send_blocked; + size_t num_blocked; + size_t num_blocked_sent; + // blocked field is effective only when send_blocked is true. + struct { + Endpoint *endpoint; + Address local_addr; + Address remote_addr; + unsigned int ecn; + const uint8_t *data; + size_t datalen; + size_t gso_size; + } blocked[2]; + std::unique_ptr<uint8_t[]> data; + } tx_; +}; + +class Server { +public: + Server(struct ev_loop *loop, TLSServerContext &tls_ctx); + ~Server(); + + int init(const char *addr, const char *port); + void disconnect(); + void close(); + + int on_read(Endpoint &ep); + int send_version_negotiation(uint32_t version, const uint8_t *dcid, + size_t dcidlen, const uint8_t *scid, + size_t scidlen, Endpoint &ep, + const Address &local_addr, const sockaddr *sa, + socklen_t salen); + int send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep, + const Address &local_addr, const sockaddr *sa, socklen_t salen, + size_t max_pktlen); + int send_stateless_connection_close(const ngtcp2_pkt_hd *chd, Endpoint &ep, + const Address &local_addr, + const sockaddr *sa, socklen_t salen); + int verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, + const sockaddr *sa, socklen_t salen); + int verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa, + socklen_t salen); + int send_packet(Endpoint &ep, const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, unsigned int ecn, + const uint8_t *data, size_t datalen); + std::pair<size_t, int> send_packet(Endpoint &ep, bool &no_gso, + const ngtcp2_addr &local_addr, + const ngtcp2_addr &remote_addr, + unsigned int ecn, const uint8_t *data, + size_t datalen, size_t gso_size); + void remove(const Handler *h); + + void associate_cid(const ngtcp2_cid *cid, Handler *h); + void dissociate_cid(const ngtcp2_cid *cid); + +private: + std::unordered_map<std::string, Handler *> handlers_; + struct ev_loop *loop_; + std::vector<Endpoint> endpoints_; + TLSServerContext &tls_ctx_; + ev_signal sigintev_; +}; + +#endif // SERVER_H diff --git a/examples/server_base.cc b/examples/server_base.cc new file mode 100644 index 0000000..aea86bd --- /dev/null +++ b/examples/server_base.cc @@ -0,0 +1,58 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "server_base.h" + +#include <cassert> +#include <array> +#include <iostream> + +#include "debug.h" + +using namespace ngtcp2; + +extern Config config; + +Buffer::Buffer(const uint8_t *data, size_t datalen) + : buf{data, data + datalen}, begin(buf.data()), tail(begin + datalen) {} +Buffer::Buffer(size_t datalen) : buf(datalen), begin(buf.data()), tail(begin) {} + +static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { + auto h = static_cast<HandlerBase *>(conn_ref->user_data); + return h->conn(); +} + +HandlerBase::HandlerBase() : conn_ref_{get_conn, this}, conn_(nullptr) { + ngtcp2_connection_close_error_default(&last_error_); +} + +HandlerBase::~HandlerBase() { + if (conn_) { + ngtcp2_conn_del(conn_); + } +} + +ngtcp2_conn *HandlerBase::conn() const { return conn_; } + +ngtcp2_crypto_conn_ref *HandlerBase::conn_ref() { return &conn_ref_; } diff --git a/examples/server_base.h b/examples/server_base.h new file mode 100644 index 0000000..adbbd20 --- /dev/null +++ b/examples/server_base.h @@ -0,0 +1,194 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SERVER_BASE_H +#define SERVER_BASE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <vector> +#include <deque> +#include <unordered_map> +#include <string> +#include <string_view> +#include <functional> + +#include <ngtcp2/ngtcp2_crypto.h> + +#include "tls_server_session.h" +#include "network.h" +#include "shared.h" + +using namespace ngtcp2; + +struct Config { + Address preferred_ipv4_addr; + Address preferred_ipv6_addr; + // tx_loss_prob is probability of losing outgoing packet. + double tx_loss_prob; + // rx_loss_prob is probability of losing incoming packet. + double rx_loss_prob; + // ciphers is the list of enabled ciphers. + const char *ciphers; + // groups is the list of supported groups. + const char *groups; + // htdocs is a root directory to serve documents. + std::string htdocs; + // mime_types_file is a path to "MIME media types and the + // extensions" file. Ubuntu mime-support package includes it in + // /etc/mime/types. + std::string_view mime_types_file; + // mime_types maps file extension to MIME media type. + std::unordered_map<std::string, std::string> mime_types; + // port is the port number which server listens on for incoming + // connections. + uint16_t port; + // quiet suppresses the output normally shown except for the error + // messages. + bool quiet; + // timeout is an idle timeout for QUIC connection. + ngtcp2_duration timeout; + // show_secret is true if transport secrets should be printed out. + bool show_secret; + // validate_addr is true if server requires address validation. + bool validate_addr; + // early_response is true if server starts sending response when it + // receives HTTP header fields without waiting for request body. If + // HTTP response data is written before receiving request body, + // STOP_SENDING is sent. + bool early_response; + // verify_client is true if server verifies client with X.509 + // certificate based authentication. + bool verify_client; + // qlog_dir is the path to directory where qlog is stored. + std::string_view qlog_dir; + // no_quic_dump is true if hexdump of QUIC STREAM and CRYPTO data + // should be disabled. + bool no_quic_dump; + // no_http_dump is true if hexdump of HTTP response body should be + // disabled. + bool no_http_dump; + // max_data is the initial connection-level flow control window. + uint64_t max_data; + // max_stream_data_bidi_local is the initial stream-level flow + // control window for a bidirectional stream that the local endpoint + // initiates. + uint64_t max_stream_data_bidi_local; + // max_stream_data_bidi_remote is the initial stream-level flow + // control window for a bidirectional stream that the remote + // endpoint initiates. + uint64_t max_stream_data_bidi_remote; + // max_stream_data_uni is the initial stream-level flow control + // window for a unidirectional stream. + uint64_t max_stream_data_uni; + // max_streams_bidi is the number of the concurrent bidirectional + // streams. + uint64_t max_streams_bidi; + // max_streams_uni is the number of the concurrent unidirectional + // streams. + uint64_t max_streams_uni; + // max_window is the maximum connection-level flow control window + // size if auto-tuning is enabled. + uint64_t max_window; + // max_stream_window is the maximum stream-level flow control window + // size if auto-tuning is enabled. + uint64_t max_stream_window; + // max_dyn_length is the maximum length of dynamically generated + // response. + uint64_t max_dyn_length; + // static_secret is used to derive keying materials for Retry and + // Stateless Retry token. + std::array<uint8_t, 32> static_secret; + // cc_algo is the congestion controller algorithm. + ngtcp2_cc_algo cc_algo; + // initial_rtt is an initial RTT. + ngtcp2_duration initial_rtt; + // max_udp_payload_size is the maximum UDP payload size that server + // transmits. + size_t max_udp_payload_size; + // send_trailers controls whether server sends trailer fields or + // not. + bool send_trailers; + // max_gso_dgrams is the maximum number of UDP datagrams in one GSO + // sendmsg call. + size_t max_gso_dgrams; + // handshake_timeout is the period of time before giving up QUIC + // connection establishment. + ngtcp2_duration handshake_timeout; + // preferred_versions includes QUIC versions in the order of + // preference. Server negotiates one of those versions if a client + // initially selects a less preferred version. + std::vector<uint32_t> preferred_versions; + // other_versions includes QUIC versions that are sent in + // other_versions field of version_information transport_parameter. + std::vector<uint32_t> other_versions; + // no_pmtud disables Path MTU Discovery. + bool no_pmtud; + // ack_thresh is the minimum number of the received ACK eliciting + // packets that triggers immediate acknowledgement. + size_t ack_thresh; +}; + +struct Buffer { + Buffer(const uint8_t *data, size_t datalen); + explicit Buffer(size_t datalen); + + size_t size() const { return tail - begin; } + size_t left() const { return buf.data() + buf.size() - tail; } + uint8_t *const wpos() { return tail; } + const uint8_t *rpos() const { return begin; } + void push(size_t len) { tail += len; } + void reset() { tail = begin; } + + std::vector<uint8_t> buf; + // begin points to the beginning of the buffer. This might point to + // buf.data() if a buffer space is allocated by this object. It is + // also allowed to point to the external shared buffer. + uint8_t *begin; + // tail points to the position of the buffer where write should + // occur. + uint8_t *tail; +}; + +class HandlerBase { +public: + HandlerBase(); + ~HandlerBase(); + + ngtcp2_conn *conn() const; + + TLSServerSession *get_session() { return &tls_session_; } + + ngtcp2_crypto_conn_ref *conn_ref(); + +protected: + ngtcp2_crypto_conn_ref conn_ref_; + TLSServerSession tls_session_; + ngtcp2_conn *conn_; + ngtcp2_connection_close_error last_error_; +}; + +#endif // SERVER_BASE_H diff --git a/examples/shared.cc b/examples/shared.cc new file mode 100644 index 0000000..d65819a --- /dev/null +++ b/examples/shared.cc @@ -0,0 +1,385 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "shared.h" + +#include <nghttp3/nghttp3.h> + +#include <cstring> +#include <cassert> +#include <iostream> + +#include <unistd.h> +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif // HAVE_NETINET_IN_H +#ifdef HAVE_ASM_TYPES_H +# include <asm/types.h> +#endif // HAVE_ASM_TYPES_H +#ifdef HAVE_LINUX_NETLINK_H +# include <linux/netlink.h> +#endif // HAVE_LINUX_NETLINK_H +#ifdef HAVE_LINUX_RTNETLINK_H +# include <linux/rtnetlink.h> +#endif // HAVE_LINUX_RTNETLINK_H + +#include "template.h" + +namespace ngtcp2 { + +unsigned int msghdr_get_ecn(msghdr *msg, int family) { + switch (family) { + case AF_INET: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TOS && + cmsg->cmsg_len) { + return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg)); + } + } + break; + case AF_INET6: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS && + cmsg->cmsg_len) { + return *reinterpret_cast<uint8_t *>(CMSG_DATA(cmsg)); + } + } + break; + } + + return 0; +} + +void fd_set_ecn(int fd, int family, unsigned int ecn) { + switch (family) { + case AF_INET: + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &ecn, + static_cast<socklen_t>(sizeof(ecn))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + } + break; + case AF_INET6: + if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &ecn, + static_cast<socklen_t>(sizeof(ecn))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + } + break; + } +} + +void fd_set_recv_ecn(int fd, int family) { + unsigned int tos = 1; + switch (family) { + case AF_INET: + if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &tos, + static_cast<socklen_t>(sizeof(tos))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + } + break; + case AF_INET6: + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &tos, + static_cast<socklen_t>(sizeof(tos))) == -1) { + std::cerr << "setsockopt: " << strerror(errno) << std::endl; + } + break; + } +} + +void fd_set_ip_mtu_discover(int fd, int family) { +#if defined(IP_MTU_DISCOVER) && defined(IPV6_MTU_DISCOVER) + int val; + + switch (family) { + case AF_INET: + val = IP_PMTUDISC_DO; + if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: IP_MTU_DISCOVER: " << strerror(errno) + << std::endl; + } + break; + case AF_INET6: + val = IPV6_PMTUDISC_DO; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: IPV6_MTU_DISCOVER: " << strerror(errno) + << std::endl; + } + break; + } +#endif // defined(IP_MTU_DISCOVER) && defined(IPV6_MTU_DISCOVER) +} + +void fd_set_ip_dontfrag(int fd, int family) { +#if defined(IP_DONTFRAG) && defined(IPV6_DONTFRAG) + int val = 1; + + switch (family) { + case AF_INET: + if (setsockopt(fd, IPPROTO_IP, IP_DONTFRAG, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: IP_DONTFRAG: " << strerror(errno) << std::endl; + } + break; + case AF_INET6: + if (setsockopt(fd, IPPROTO_IPV6, IPV6_DONTFRAG, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + std::cerr << "setsockopt: IPV6_DONTFRAG: " << strerror(errno) + << std::endl; + } + break; + } +#endif // defined(IP_DONTFRAG) && defined(IPV6_DONTFRAG) +} + +std::optional<Address> msghdr_get_local_addr(msghdr *msg, int family) { + switch (family) { + case AF_INET: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) { + auto pktinfo = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cmsg)); + Address res{}; + res.ifindex = pktinfo->ipi_ifindex; + res.len = sizeof(res.su.in); + auto &sa = res.su.in; + sa.sin_family = AF_INET; + sa.sin_addr = pktinfo->ipi_addr; + return res; + } + } + return {}; + case AF_INET6: + for (auto cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { + auto pktinfo = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsg)); + Address res{}; + res.ifindex = pktinfo->ipi6_ifindex; + res.len = sizeof(res.su.in6); + auto &sa = res.su.in6; + sa.sin6_family = AF_INET6; + sa.sin6_addr = pktinfo->ipi6_addr; + return res; + } + } + return {}; + } + return {}; +} + +void set_port(Address &dst, Address &src) { + switch (dst.su.storage.ss_family) { + case AF_INET: + assert(AF_INET == src.su.storage.ss_family); + dst.su.in.sin_port = src.su.in.sin_port; + return; + case AF_INET6: + assert(AF_INET6 == src.su.storage.ss_family); + dst.su.in6.sin6_port = src.su.in6.sin6_port; + return; + default: + assert(0); + } +} + +#ifdef HAVE_LINUX_RTNETLINK_H + +struct nlmsg { + nlmsghdr hdr; + rtmsg msg; + rtattr dst; + in_addr_union dst_addr; +}; + +namespace { +int send_netlink_msg(int fd, const Address &remote_addr) { + nlmsg nlmsg{}; + nlmsg.hdr.nlmsg_type = RTM_GETROUTE; + nlmsg.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + nlmsg.msg.rtm_family = remote_addr.su.sa.sa_family; + + nlmsg.dst.rta_type = RTA_DST; + + switch (remote_addr.su.sa.sa_family) { + case AF_INET: + nlmsg.dst.rta_len = RTA_LENGTH(sizeof(remote_addr.su.in.sin_addr)); + memcpy(RTA_DATA(&nlmsg.dst), &remote_addr.su.in.sin_addr, + sizeof(remote_addr.su.in.sin_addr)); + break; + case AF_INET6: + nlmsg.dst.rta_len = RTA_LENGTH(sizeof(remote_addr.su.in6.sin6_addr)); + memcpy(RTA_DATA(&nlmsg.dst), &remote_addr.su.in6.sin6_addr, + sizeof(remote_addr.su.in6.sin6_addr)); + break; + default: + assert(0); + } + + nlmsg.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(nlmsg.msg) + nlmsg.dst.rta_len); + + sockaddr_nl sa{}; + sa.nl_family = AF_NETLINK; + + iovec iov{&nlmsg, nlmsg.hdr.nlmsg_len}; + msghdr msg{}; + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + ssize_t nwrite; + + do { + nwrite = sendmsg(fd, &msg, 0); + } while (nwrite == -1 && errno == EINTR); + + if (nwrite == -1) { + std::cerr << "sendmsg: Could not write netlink message: " << strerror(errno) + << std::endl; + return -1; + } + + return 0; +} +} // namespace + +namespace { +int recv_netlink_msg(in_addr_union &iau, int fd) { + std::array<uint8_t, 8192> buf; + iovec iov = {buf.data(), buf.size()}; + sockaddr_nl sa{}; + msghdr msg{}; + + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + ssize_t nread; + + do { + nread = recvmsg(fd, &msg, 0); + } while (nread == -1 && errno == EINTR); + + if (nread == -1) { + std::cerr << "recvmsg: Could not receive netlink message: " + << strerror(errno) << std::endl; + return -1; + } + + for (auto hdr = reinterpret_cast<nlmsghdr *>(buf.data()); + NLMSG_OK(hdr, nread); hdr = NLMSG_NEXT(hdr, nread)) { + switch (hdr->nlmsg_type) { + case NLMSG_DONE: + std::cerr << "netlink: no info returned from kernel" << std::endl; + return -1; + case NLMSG_NOOP: + continue; + case NLMSG_ERROR: + std::cerr << "netlink: " + << strerror(-static_cast<nlmsgerr *>(NLMSG_DATA(hdr))->error) + << std::endl; + return -1; + } + + auto attrlen = hdr->nlmsg_len - NLMSG_SPACE(sizeof(rtmsg)); + + for (auto rta = reinterpret_cast<rtattr *>( + static_cast<uint8_t *>(NLMSG_DATA(hdr)) + sizeof(rtmsg)); + RTA_OK(rta, attrlen); rta = RTA_NEXT(rta, attrlen)) { + if (rta->rta_type != RTA_PREFSRC) { + continue; + } + + size_t in_addrlen; + + switch (static_cast<rtmsg *>(NLMSG_DATA(hdr))->rtm_family) { + case AF_INET: + in_addrlen = sizeof(in_addr); + break; + case AF_INET6: + in_addrlen = sizeof(in6_addr); + break; + default: + assert(0); + abort(); + } + + if (RTA_LENGTH(in_addrlen) != rta->rta_len) { + return -1; + } + + memcpy(&iau, RTA_DATA(rta), in_addrlen); + + return 0; + } + } + + return -1; +} +} // namespace + +int get_local_addr(in_addr_union &iau, const Address &remote_addr) { + sockaddr_nl sa{}; + sa.nl_family = AF_NETLINK; + + auto fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd == -1) { + std::cerr << "socket: Could not create netlink socket: " << strerror(errno) + << std::endl; + return -1; + } + + auto fd_d = defer(close, fd); + + if (bind(fd, reinterpret_cast<sockaddr *>(&sa), sizeof(sa)) == -1) { + std::cerr << "bind: Could not bind netlink socket: " << strerror(errno) + << std::endl; + return -1; + } + + if (send_netlink_msg(fd, remote_addr) != 0) { + return -1; + } + + return recv_netlink_msg(iau, fd); +} + +#endif // HAVE_LINUX_NETLINK_H + +bool addreq(const sockaddr *sa, const in_addr_union &iau) { + switch (sa->sa_family) { + case AF_INET: + return memcmp(&reinterpret_cast<const sockaddr_in *>(sa)->sin_addr, &iau.in, + sizeof(iau.in)) == 0; + case AF_INET6: + return memcmp(&reinterpret_cast<const sockaddr_in6 *>(sa)->sin6_addr, + &iau.in6, sizeof(iau.in6)) == 0; + default: + assert(0); + abort(); + } +} + +} // namespace ngtcp2 diff --git a/examples/shared.h b/examples/shared.h new file mode 100644 index 0000000..e0e4cec --- /dev/null +++ b/examples/shared.h @@ -0,0 +1,96 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef SHARED_H +#define SHARED_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <optional> + +#include <ngtcp2/ngtcp2.h> + +#include "network.h" + +namespace ngtcp2 { + +enum class AppProtocol { + H3, + HQ, +}; + +constexpr uint8_t HQ_ALPN[] = "\xahq-interop\x5hq-29\x5hq-30\x5hq-31\x5hq-32"; +constexpr uint8_t HQ_ALPN_DRAFT29[] = "\x5hq-29"; +constexpr uint8_t HQ_ALPN_DRAFT30[] = "\x5hq-30"; +constexpr uint8_t HQ_ALPN_DRAFT31[] = "\x5hq-31"; +constexpr uint8_t HQ_ALPN_DRAFT32[] = "\x5hq-32"; +constexpr uint8_t HQ_ALPN_V1[] = "\xahq-interop"; + +constexpr uint8_t H3_ALPN[] = "\x2h3\x5h3-29\x5h3-30\x5h3-31\x5h3-32"; +constexpr uint8_t H3_ALPN_DRAFT29[] = "\x5h3-29"; +constexpr uint8_t H3_ALPN_DRAFT30[] = "\x5h3-30"; +constexpr uint8_t H3_ALPN_DRAFT31[] = "\x5h3-31"; +constexpr uint8_t H3_ALPN_DRAFT32[] = "\x5h3-32"; +constexpr uint8_t H3_ALPN_V1[] = "\x2h3"; + +constexpr uint32_t QUIC_VER_DRAFT29 = 0xff00001du; +constexpr uint32_t QUIC_VER_DRAFT30 = 0xff00001eu; +constexpr uint32_t QUIC_VER_DRAFT31 = 0xff00001fu; +constexpr uint32_t QUIC_VER_DRAFT32 = 0xff000020u; + +// msghdr_get_ecn gets ECN bits from |msg|. |family| is the address +// family from which packet is received. +unsigned int msghdr_get_ecn(msghdr *msg, int family); + +// fd_set_ecn sets ECN bits |ecn| to |fd|. |family| is the address +// family of |fd|. +void fd_set_ecn(int fd, int family, unsigned int ecn); + +// fd_set_recv_ecn sets socket option to |fd| so that it can receive +// ECN bits. +void fd_set_recv_ecn(int fd, int family); + +// fd_set_ip_mtu_discover sets IP(V6)_MTU_DISCOVER socket option to +// |fd|. +void fd_set_ip_mtu_discover(int fd, int family); + +// fd_set_ip_dontfrag sets IP(V6)_DONTFRAG socket option to |fd|. +void fd_set_ip_dontfrag(int fd, int family); + +std::optional<Address> msghdr_get_local_addr(msghdr *msg, int family); + +void set_port(Address &dst, Address &src); + +// get_local_addr stores preferred local address (interface address) +// in |iau| for a given destination address |remote_addr|. +int get_local_addr(in_addr_union &iau, const Address &remote_addr); + +// addreq returns true if |sa| and |iau| contain the same address. +bool addreq(const sockaddr *sa, const in_addr_union &iau); + +} // namespace ngtcp2 + +#endif // SHARED_H diff --git a/examples/simpleclient.c b/examples/simpleclient.c new file mode 100644 index 0000000..59321b5 --- /dev/null +++ b/examples/simpleclient.c @@ -0,0 +1,683 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#include <ngtcp2/ngtcp2.h> +#include <ngtcp2/ngtcp2_crypto.h> +#include <ngtcp2/ngtcp2_crypto_openssl.h> + +#include <openssl/ssl.h> +#include <openssl/rand.h> +#include <openssl/err.h> + +#include <ev.h> + +#define REMOTE_HOST "127.0.0.1" +#define REMOTE_PORT "4433" +#define ALPN "\xahq-interop" +#define MESSAGE "GET /\r\n" + +/* + * Example 1: Handshake with www.google.com + * + * #define REMOTE_HOST "www.google.com" + * #define REMOTE_PORT "443" + * #define ALPN "\x2h3" + * + * and undefine MESSAGE macro. + */ + +static uint64_t timestamp(void) { + struct timespec tp; + + if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { + fprintf(stderr, "clock_gettime: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + return (uint64_t)tp.tv_sec * NGTCP2_SECONDS + (uint64_t)tp.tv_nsec; +} + +static int create_sock(struct sockaddr *addr, socklen_t *paddrlen, + const char *host, const char *port) { + struct addrinfo hints = {0}; + struct addrinfo *res, *rp; + int rv; + int fd = -1; + + hints.ai_flags = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + + rv = getaddrinfo(host, port, &hints, &res); + if (rv != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return -1; + } + + for (rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + continue; + } + + break; + } + + if (fd == -1) { + goto end; + } + + *paddrlen = rp->ai_addrlen; + memcpy(addr, rp->ai_addr, rp->ai_addrlen); + +end: + freeaddrinfo(res); + + return fd; +} + +static int connect_sock(struct sockaddr *local_addr, socklen_t *plocal_addrlen, + int fd, const struct sockaddr *remote_addr, + size_t remote_addrlen) { + socklen_t len; + + if (connect(fd, remote_addr, (socklen_t)remote_addrlen) != 0) { + fprintf(stderr, "connect: %s\n", strerror(errno)); + return -1; + } + + len = *plocal_addrlen; + + if (getsockname(fd, local_addr, &len) == -1) { + fprintf(stderr, "getsockname: %s\n", strerror(errno)); + return -1; + } + + *plocal_addrlen = len; + + return 0; +} + +struct client { + ngtcp2_crypto_conn_ref conn_ref; + int fd; + struct sockaddr_storage local_addr; + socklen_t local_addrlen; + SSL_CTX *ssl_ctx; + SSL *ssl; + ngtcp2_conn *conn; + + struct { + int64_t stream_id; + const uint8_t *data; + size_t datalen; + size_t nwrite; + } stream; + + ngtcp2_connection_close_error last_error; + + ev_io rev; + ev_timer timer; +}; + +static int numeric_host_family(const char *hostname, int family) { + uint8_t dst[sizeof(struct in6_addr)]; + return inet_pton(family, hostname, dst) == 1; +} + +static int numeric_host(const char *hostname) { + return numeric_host_family(hostname, AF_INET) || + numeric_host_family(hostname, AF_INET6); +} + +static int client_ssl_init(struct client *c) { + c->ssl_ctx = SSL_CTX_new(TLS_client_method()); + if (!c->ssl_ctx) { + fprintf(stderr, "SSL_CTX_new: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } + + if (ngtcp2_crypto_openssl_configure_client_context(c->ssl_ctx) != 0) { + fprintf(stderr, "ngtcp2_crypto_openssl_configure_client_context failed\n"); + return -1; + } + + c->ssl = SSL_new(c->ssl_ctx); + if (!c->ssl) { + fprintf(stderr, "SSL_new: %s\n", ERR_error_string(ERR_get_error(), NULL)); + return -1; + } + + SSL_set_app_data(c->ssl, &c->conn_ref); + SSL_set_connect_state(c->ssl); + SSL_set_alpn_protos(c->ssl, (const unsigned char *)ALPN, sizeof(ALPN) - 1); + if (!numeric_host(REMOTE_HOST)) { + SSL_set_tlsext_host_name(c->ssl, REMOTE_HOST); + } + + /* For NGTCP2_PROTO_VER_V1 */ + SSL_set_quic_transport_version(c->ssl, TLSEXT_TYPE_quic_transport_parameters); + + return 0; +} + +static void rand_cb(uint8_t *dest, size_t destlen, + const ngtcp2_rand_ctx *rand_ctx) { + size_t i; + (void)rand_ctx; + + for (i = 0; i < destlen; ++i) { + *dest = (uint8_t)random(); + } +} + +static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data) { + (void)conn; + (void)user_data; + + if (RAND_bytes(cid->data, (int)cidlen) != 1) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + cid->datalen = cidlen; + + if (RAND_bytes(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 1) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int extend_max_local_streams_bidi(ngtcp2_conn *conn, + uint64_t max_streams, + void *user_data) { +#ifdef MESSAGE + struct client *c = user_data; + int rv; + int64_t stream_id; + (void)max_streams; + + if (c->stream.stream_id != -1) { + return 0; + } + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + if (rv != 0) { + return 0; + } + + c->stream.stream_id = stream_id; + c->stream.data = (const uint8_t *)MESSAGE; + c->stream.datalen = sizeof(MESSAGE) - 1; + + return 0; +#else /* !MESSAGE */ + (void)conn; + (void)max_streams; + (void)user_data; + + return 0; +#endif /* !MESSAGE */ +} + +static void log_printf(void *user_data, const char *fmt, ...) { + va_list ap; + (void)user_data; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fprintf(stderr, "\n"); +} + +static int client_quic_init(struct client *c, + const struct sockaddr *remote_addr, + socklen_t remote_addrlen, + const struct sockaddr *local_addr, + socklen_t local_addrlen) { + ngtcp2_path path = { + { + (struct sockaddr *)local_addr, + local_addrlen, + }, + { + (struct sockaddr *)remote_addr, + remote_addrlen, + }, + NULL, + }; + ngtcp2_callbacks callbacks = { + ngtcp2_crypto_client_initial_cb, + NULL, /* recv_client_initial */ + ngtcp2_crypto_recv_crypto_data_cb, + NULL, /* handshake_completed */ + NULL, /* recv_version_negotiation */ + ngtcp2_crypto_encrypt_cb, + ngtcp2_crypto_decrypt_cb, + ngtcp2_crypto_hp_mask_cb, + NULL, /* recv_stream_data */ + NULL, /* acked_stream_data_offset */ + NULL, /* stream_open */ + NULL, /* stream_close */ + NULL, /* recv_stateless_reset */ + ngtcp2_crypto_recv_retry_cb, + extend_max_local_streams_bidi, + NULL, /* extend_max_local_streams_uni */ + rand_cb, + get_new_connection_id_cb, + NULL, /* remove_connection_id */ + ngtcp2_crypto_update_key_cb, + NULL, /* path_validation */ + NULL, /* select_preferred_address */ + NULL, /* stream_reset */ + NULL, /* extend_max_remote_streams_bidi */ + NULL, /* extend_max_remote_streams_uni */ + NULL, /* extend_max_stream_data */ + NULL, /* dcid_status */ + NULL, /* handshake_confirmed */ + NULL, /* recv_new_token */ + ngtcp2_crypto_delete_crypto_aead_ctx_cb, + ngtcp2_crypto_delete_crypto_cipher_ctx_cb, + NULL, /* recv_datagram */ + NULL, /* ack_datagram */ + NULL, /* lost_datagram */ + ngtcp2_crypto_get_path_challenge_data_cb, + NULL, /* stream_stop_sending */ + ngtcp2_crypto_version_negotiation_cb, + NULL, /* recv_rx_key */ + NULL, /* recv_tx_key */ + NULL, /* early_data_rejected */ + }; + ngtcp2_cid dcid, scid; + ngtcp2_settings settings; + ngtcp2_transport_params params; + int rv; + + dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN; + if (RAND_bytes(dcid.data, (int)dcid.datalen) != 1) { + fprintf(stderr, "RAND_bytes failed\n"); + return -1; + } + + scid.datalen = 8; + if (RAND_bytes(scid.data, (int)scid.datalen) != 1) { + fprintf(stderr, "RAND_bytes failed\n"); + return -1; + } + + ngtcp2_settings_default(&settings); + + settings.initial_ts = timestamp(); + settings.log_printf = log_printf; + + ngtcp2_transport_params_default(¶ms); + + params.initial_max_streams_uni = 3; + params.initial_max_stream_data_bidi_local = 128 * 1024; + params.initial_max_data = 1024 * 1024; + + rv = + ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, + &callbacks, &settings, ¶ms, NULL, c); + if (rv != 0) { + fprintf(stderr, "ngtcp2_conn_client_new: %s\n", ngtcp2_strerror(rv)); + return -1; + } + + ngtcp2_conn_set_tls_native_handle(c->conn, c->ssl); + + return 0; +} + +static int client_read(struct client *c) { + uint8_t buf[65536]; + struct sockaddr_storage addr; + struct iovec iov = {buf, sizeof(buf)}; + struct msghdr msg = {0}; + ssize_t nread; + ngtcp2_path path; + ngtcp2_pkt_info pi = {0}; + int rv; + + msg.msg_name = &addr; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + for (;;) { + msg.msg_namelen = sizeof(addr); + + nread = recvmsg(c->fd, &msg, MSG_DONTWAIT); + + if (nread == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + fprintf(stderr, "recvmsg: %s\n", strerror(errno)); + } + + break; + } + + path.local.addrlen = c->local_addrlen; + path.local.addr = (struct sockaddr *)&c->local_addr; + path.remote.addrlen = msg.msg_namelen; + path.remote.addr = msg.msg_name; + + rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (size_t)nread, + timestamp()); + if (rv != 0) { + fprintf(stderr, "ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv)); + if (!c->last_error.error_code) { + if (rv == NGTCP2_ERR_CRYPTO) { + ngtcp2_connection_close_error_set_transport_error_tls_alert( + &c->last_error, ngtcp2_conn_get_tls_alert(c->conn), NULL, 0); + } else { + ngtcp2_connection_close_error_set_transport_error_liberr( + &c->last_error, rv, NULL, 0); + } + } + return -1; + } + } + + return 0; +} + +static int client_send_packet(struct client *c, const uint8_t *data, + size_t datalen) { + struct iovec iov = {(uint8_t *)data, datalen}; + struct msghdr msg = {0}; + ssize_t nwrite; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + do { + nwrite = sendmsg(c->fd, &msg, 0); + } while (nwrite == -1 && errno == EINTR); + + if (nwrite == -1) { + fprintf(stderr, "sendmsg: %s\n", strerror(errno)); + + return -1; + } + + return 0; +} + +static size_t client_get_message(struct client *c, int64_t *pstream_id, + int *pfin, ngtcp2_vec *datav, + size_t datavcnt) { + if (datavcnt == 0) { + return 0; + } + + if (c->stream.stream_id != -1 && c->stream.nwrite < c->stream.datalen) { + *pstream_id = c->stream.stream_id; + *pfin = 1; + datav->base = (uint8_t *)c->stream.data + c->stream.nwrite; + datav->len = c->stream.datalen - c->stream.nwrite; + return 1; + } + + *pstream_id = -1; + *pfin = 0; + datav->base = NULL; + datav->len = 0; + + return 0; +} + +static int client_write_streams(struct client *c) { + ngtcp2_tstamp ts = timestamp(); + ngtcp2_pkt_info pi; + ngtcp2_ssize nwrite; + uint8_t buf[1280]; + ngtcp2_path_storage ps; + ngtcp2_vec datav; + size_t datavcnt; + int64_t stream_id; + ngtcp2_ssize wdatalen; + uint32_t flags; + int fin; + + ngtcp2_path_storage_zero(&ps); + + for (;;) { + datavcnt = client_get_message(c, &stream_id, &fin, &datav, 1); + + flags = NGTCP2_WRITE_STREAM_FLAG_MORE; + if (fin) { + flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; + } + + nwrite = ngtcp2_conn_writev_stream(c->conn, &ps.path, &pi, buf, sizeof(buf), + &wdatalen, flags, stream_id, &datav, + datavcnt, ts); + if (nwrite < 0) { + switch (nwrite) { + case NGTCP2_ERR_WRITE_MORE: + c->stream.nwrite += (size_t)wdatalen; + continue; + default: + fprintf(stderr, "ngtcp2_conn_writev_stream: %s\n", + ngtcp2_strerror((int)nwrite)); + ngtcp2_connection_close_error_set_transport_error_liberr( + &c->last_error, (int)nwrite, NULL, 0); + return -1; + } + } + + if (nwrite == 0) { + return 0; + } + + if (wdatalen > 0) { + c->stream.nwrite += (size_t)wdatalen; + } + + if (client_send_packet(c, buf, (size_t)nwrite) != 0) { + break; + } + } + + return 0; +} + +static int client_write(struct client *c) { + ngtcp2_tstamp expiry, now; + ev_tstamp t; + + if (client_write_streams(c) != 0) { + return -1; + } + + expiry = ngtcp2_conn_get_expiry(c->conn); + now = timestamp(); + + t = expiry < now ? 1e-9 : (ev_tstamp)(expiry - now) / NGTCP2_SECONDS; + + c->timer.repeat = t; + ev_timer_again(EV_DEFAULT, &c->timer); + + return 0; +} + +static int client_handle_expiry(struct client *c) { + int rv = ngtcp2_conn_handle_expiry(c->conn, timestamp()); + if (rv != 0) { + fprintf(stderr, "ngtcp2_conn_handle_expiry: %s\n", ngtcp2_strerror(rv)); + return -1; + } + + return 0; +} + +static void client_close(struct client *c) { + ngtcp2_ssize nwrite; + ngtcp2_pkt_info pi; + ngtcp2_path_storage ps; + uint8_t buf[1280]; + + if (ngtcp2_conn_is_in_closing_period(c->conn) || + ngtcp2_conn_is_in_draining_period(c->conn)) { + goto fin; + } + + ngtcp2_path_storage_zero(&ps); + + nwrite = ngtcp2_conn_write_connection_close( + c->conn, &ps.path, &pi, buf, sizeof(buf), &c->last_error, timestamp()); + if (nwrite < 0) { + fprintf(stderr, "ngtcp2_conn_write_connection_close: %s\n", + ngtcp2_strerror((int)nwrite)); + goto fin; + } + + client_send_packet(c, buf, (size_t)nwrite); + +fin: + ev_break(EV_DEFAULT, EVBREAK_ALL); +} + +static void read_cb(struct ev_loop *loop, ev_io *w, int revents) { + struct client *c = w->data; + (void)loop; + (void)revents; + + if (client_read(c) != 0) { + client_close(c); + return; + } + + if (client_write(c) != 0) { + client_close(c); + } +} + +static void timer_cb(struct ev_loop *loop, ev_timer *w, int revents) { + struct client *c = w->data; + (void)loop; + (void)revents; + + if (client_handle_expiry(c) != 0) { + client_close(c); + return; + } + + if (client_write(c) != 0) { + client_close(c); + } +} + +static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { + struct client *c = conn_ref->user_data; + return c->conn; +} + +static int client_init(struct client *c) { + struct sockaddr_storage remote_addr, local_addr; + socklen_t remote_addrlen, local_addrlen = sizeof(local_addr); + + memset(c, 0, sizeof(*c)); + + ngtcp2_connection_close_error_default(&c->last_error); + + c->fd = create_sock((struct sockaddr *)&remote_addr, &remote_addrlen, + REMOTE_HOST, REMOTE_PORT); + if (c->fd == -1) { + return -1; + } + + if (connect_sock((struct sockaddr *)&local_addr, &local_addrlen, c->fd, + (struct sockaddr *)&remote_addr, remote_addrlen) != 0) { + return -1; + } + + memcpy(&c->local_addr, &local_addr, sizeof(c->local_addr)); + c->local_addrlen = local_addrlen; + + if (client_ssl_init(c) != 0) { + return -1; + } + + if (client_quic_init(c, (struct sockaddr *)&remote_addr, remote_addrlen, + (struct sockaddr *)&local_addr, local_addrlen) != 0) { + return -1; + } + + c->stream.stream_id = -1; + + c->conn_ref.get_conn = get_conn; + c->conn_ref.user_data = c; + + ev_io_init(&c->rev, read_cb, c->fd, EV_READ); + c->rev.data = c; + ev_io_start(EV_DEFAULT, &c->rev); + + ev_timer_init(&c->timer, timer_cb, 0., 0.); + c->timer.data = c; + + return 0; +} + +static void client_free(struct client *c) { + ngtcp2_conn_del(c->conn); + SSL_free(c->ssl); + SSL_CTX_free(c->ssl_ctx); +} + +int main(void) { + struct client c; + + srandom((unsigned int)timestamp()); + + if (client_init(&c) != 0) { + exit(EXIT_FAILURE); + } + + if (client_write(&c) != 0) { + exit(EXIT_FAILURE); + } + + ev_run(EV_DEFAULT, 0); + + client_free(&c); + + return 0; +} diff --git a/examples/template.h b/examples/template.h new file mode 100644 index 0000000..e923fae --- /dev/null +++ b/examples/template.h @@ -0,0 +1,71 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2015 ngttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TEMPLATE_H +#define TEMPLATE_H + +#include <functional> +#include <utility> +#include <type_traits> + +// inspired by <http://blog.korfuri.fr/post/go-defer-in-cpp/>, but our +// template can take functions returning other than void. +template <typename F, typename... T> struct Defer { + Defer(F &&f, T &&...t) + : f(std::bind(std::forward<F>(f), std::forward<T>(t)...)) {} + Defer(Defer &&o) noexcept : f(std::move(o.f)) {} + ~Defer() { f(); } + + using ResultType = std::invoke_result_t<F, T...>; + std::function<ResultType()> f; +}; + +template <typename F, typename... T> Defer<F, T...> defer(F &&f, T &&...t) { + return Defer<F, T...>(std::forward<F>(f), std::forward<T>(t)...); +} + +template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) { + return N; +} + +template <typename T, size_t N> constexpr size_t str_size(T (&)[N]) { + return N - 1; +} + +// User-defined literals for K, M, and G (powers of 1024) + +constexpr unsigned long long operator"" _k(unsigned long long k) { + return k * 1024; +} + +constexpr unsigned long long operator"" _m(unsigned long long m) { + return m * 1024 * 1024; +} + +constexpr unsigned long long operator"" _g(unsigned long long g) { + return g * 1024 * 1024 * 1024; +} + +#endif // TEMPLATE_H diff --git a/examples/tests/.gitignore b/examples/tests/.gitignore new file mode 100644 index 0000000..ba92e76 --- /dev/null +++ b/examples/tests/.gitignore @@ -0,0 +1,3 @@ +config.ini +gen +*.bak diff --git a/examples/tests/README.rst b/examples/tests/README.rst new file mode 100644 index 0000000..f583ecf --- /dev/null +++ b/examples/tests/README.rst @@ -0,0 +1,60 @@ +Examples Tests +============== + +This is a ``pytest`` suite intended to verify interoperability between +the different example clients and servers built. + +You run it by executing ``pytest`` on top level project dir or in +the examples/tests directory. + +.. code-block:: text + + examples/test> pytest + ngtcp2-examples: [0.9.0-DEV, crypto_libs=['openssl', 'wolfssl']] + ... + +Requirements +------------ + +You need a Python3 (3.8 is probably sufficient), ``pytest`` and the +Python ``cryptography`` module installed. + +Usage +----- + +If you run ``pytest`` without arguments, it will print the test suite +and a ``.`` for every test case passed. Add ``-v`` and all test cases +will be listed in the full name. Adding several ``v`` will increase the +logging level on failure output. + +The name of test cases include the crypto libs of the server and client +used. For example: + +.. code-block:: text + + test_01_handshake.py::TestHandshake::test_01_01_get[openssl-openssl] PASSED [ 16%] + test_01_handshake.py::TestHandshake::test_01_01_get[openssl-wolfssl] PASSED + +Here, ``test_01_01`` is run first with the OpenSSL server and client and then +with the OpenSSL server and wolfSSL client. By default, the test suite runs +all combinations of servers and clients that have been configured in the project. + +To track down problems, you can restrict the test cases that are run by +matching patterns: + +.. code-block:: text + + # only tests with wolfSSL example server + > pytest -v -k 'wolfssl-' + # only tests with wolfSSL example client + > pytest -v -k 'test and -wolfssl' + # tests with a specific combination + > pytest -v -k 'openssl-wolfssl' + + +Analysing +--------- + +To make analysis of a broken test case easier, you best run only that +test case. Use ``pytest -vv`` (or more) to get more verbose logging. +Inspect server and client log files in ``examples/tests/gen``. diff --git a/examples/tests/__init__.py b/examples/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/examples/tests/__init__.py diff --git a/examples/tests/config.ini.in b/examples/tests/config.ini.in new file mode 100644 index 0000000..2d15dbb --- /dev/null +++ b/examples/tests/config.ini.in @@ -0,0 +1,32 @@ +[ngtcp2] +version = @PACKAGE_VERSION@ +examples = @EXAMPLES_ENABLED@ + +[crypto] +openssl = @have_openssl@ +gnutls = @have_gnutls@ +boringssl = @have_boringssl@ +picotls = @have_picotls@ +wolfssl = @have_wolfssl@ + +[examples] +port = 4433 +openssl = @EXAMPLES_OPENSSL@ +gnutls = @EXAMPLES_GNUTLS@ +boringssl = @EXAMPLES_BORINGSSL@ +picotls = @EXAMPLES_PICOTLS@ +wolfssl = @EXAMPLES_WOLFSSL@ + +[clients] +openssl = client +gnutls = gtlsclient +boringssl = bsslclient +picotls = ptlsclient +wolfssl = wsslclient + +[servers] +openssl = server +gnutls = gtlsserver +boringssl = bsslserver +picotls = ptlsserver +wolfssl = wsslserver diff --git a/examples/tests/conftest.py b/examples/tests/conftest.py new file mode 100644 index 0000000..a566ff0 --- /dev/null +++ b/examples/tests/conftest.py @@ -0,0 +1,28 @@ +import logging +import pytest + +from .ngtcp2test import Env + + +@pytest.mark.usefixtures("env") +def pytest_report_header(config): + env = Env() + return [ + f"ngtcp2-examples: [{env.version}, crypto_libs={env.crypto_libs}]", + f"example clients: {env.clients}", + f"example servers: {env.servers}", + ] + + +@pytest.fixture(scope="package") +def env(pytestconfig) -> Env: + console = logging.StreamHandler() + console.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) + logging.getLogger('').addHandler(console) + env = Env(pytestconfig=pytestconfig) + level = logging.DEBUG if env.verbose > 0 else logging.INFO + console.setLevel(level) + logging.getLogger('').setLevel(level=level) + env.setup() + + return env diff --git a/examples/tests/ngtcp2test/__init__.py b/examples/tests/ngtcp2test/__init__.py new file mode 100644 index 0000000..65c61d8 --- /dev/null +++ b/examples/tests/ngtcp2test/__init__.py @@ -0,0 +1,6 @@ +from .env import Env, CryptoLib +from .log import LogFile +from .client import ExampleClient, ClientRun +from .server import ExampleServer, ServerRun +from .certs import Ngtcp2TestCA, Credentials +from .tls import HandShake, HSRecord diff --git a/examples/tests/ngtcp2test/certs.py b/examples/tests/ngtcp2test/certs.py new file mode 100644 index 0000000..3ab6260 --- /dev/null +++ b/examples/tests/ngtcp2test/certs.py @@ -0,0 +1,476 @@ +import os +import re +from datetime import timedelta, datetime +from typing import List, Any, Optional + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption, load_pem_private_key +from cryptography.x509 import ExtendedKeyUsageOID, NameOID + + +EC_SUPPORTED = {} +EC_SUPPORTED.update([(curve.name.upper(), curve) for curve in [ + ec.SECP192R1, + ec.SECP224R1, + ec.SECP256R1, + ec.SECP384R1, +]]) + + +def _private_key(key_type): + if isinstance(key_type, str): + key_type = key_type.upper() + m = re.match(r'^(RSA)?(\d+)$', key_type) + if m: + key_type = int(m.group(2)) + + if isinstance(key_type, int): + return rsa.generate_private_key( + public_exponent=65537, + key_size=key_type, + backend=default_backend() + ) + if not isinstance(key_type, ec.EllipticCurve) and key_type in EC_SUPPORTED: + key_type = EC_SUPPORTED[key_type] + return ec.generate_private_key( + curve=key_type, + backend=default_backend() + ) + + +class CertificateSpec: + + def __init__(self, name: str = None, domains: List[str] = None, + email: str = None, + key_type: str = None, single_file: bool = False, + valid_from: timedelta = timedelta(days=-1), + valid_to: timedelta = timedelta(days=89), + client: bool = False, + sub_specs: List['CertificateSpec'] = None): + self._name = name + self.domains = domains + self.client = client + self.email = email + self.key_type = key_type + self.single_file = single_file + self.valid_from = valid_from + self.valid_to = valid_to + self.sub_specs = sub_specs + + @property + def name(self) -> Optional[str]: + if self._name: + return self._name + elif self.domains: + return self.domains[0] + return None + + @property + def type(self) -> Optional[str]: + if self.domains and len(self.domains): + return "server" + elif self.client: + return "client" + elif self.name: + return "ca" + return None + + +class Credentials: + + def __init__(self, name: str, cert: Any, pkey: Any, issuer: 'Credentials' = None): + self._name = name + self._cert = cert + self._pkey = pkey + self._issuer = issuer + self._cert_file = None + self._pkey_file = None + self._store = None + + @property + def name(self) -> str: + return self._name + + @property + def subject(self) -> x509.Name: + return self._cert.subject + + @property + def key_type(self): + if isinstance(self._pkey, RSAPrivateKey): + return f"rsa{self._pkey.key_size}" + elif isinstance(self._pkey, EllipticCurvePrivateKey): + return f"{self._pkey.curve.name}" + else: + raise Exception(f"unknown key type: {self._pkey}") + + @property + def private_key(self) -> Any: + return self._pkey + + @property + def certificate(self) -> Any: + return self._cert + + @property + def cert_pem(self) -> bytes: + return self._cert.public_bytes(Encoding.PEM) + + @property + def pkey_pem(self) -> bytes: + return self._pkey.private_bytes( + Encoding.PEM, + PrivateFormat.TraditionalOpenSSL if self.key_type.startswith('rsa') else PrivateFormat.PKCS8, + NoEncryption()) + + @property + def issuer(self) -> Optional['Credentials']: + return self._issuer + + def set_store(self, store: 'CertStore'): + self._store = store + + def set_files(self, cert_file: str, pkey_file: str = None): + self._cert_file = cert_file + self._pkey_file = pkey_file + + @property + def cert_file(self) -> str: + return self._cert_file + + @property + def pkey_file(self) -> Optional[str]: + return self._pkey_file + + def get_first(self, name) -> Optional['Credentials']: + creds = self._store.get_credentials_for_name(name) if self._store else [] + return creds[0] if len(creds) else None + + def get_credentials_for_name(self, name) -> List['Credentials']: + return self._store.get_credentials_for_name(name) if self._store else [] + + def issue_certs(self, specs: List[CertificateSpec], + chain: List['Credentials'] = None) -> List['Credentials']: + return [self.issue_cert(spec=spec, chain=chain) for spec in specs] + + def issue_cert(self, spec: CertificateSpec, chain: List['Credentials'] = None) -> 'Credentials': + key_type = spec.key_type if spec.key_type else self.key_type + creds = None + if self._store: + creds = self._store.load_credentials( + name=spec.name, key_type=key_type, single_file=spec.single_file, issuer=self) + if creds is None: + creds = Ngtcp2TestCA.create_credentials(spec=spec, issuer=self, key_type=key_type, + valid_from=spec.valid_from, valid_to=spec.valid_to) + if self._store: + self._store.save(creds, single_file=spec.single_file) + if spec.type == "ca": + self._store.save_chain(creds, "ca", with_root=True) + + if spec.sub_specs: + if self._store: + sub_store = CertStore(fpath=os.path.join(self._store.path, creds.name)) + creds.set_store(sub_store) + subchain = chain.copy() if chain else [] + subchain.append(self) + creds.issue_certs(spec.sub_specs, chain=subchain) + return creds + + +class CertStore: + + def __init__(self, fpath: str): + self._store_dir = fpath + if not os.path.exists(self._store_dir): + os.makedirs(self._store_dir) + self._creds_by_name = {} + + @property + def path(self) -> str: + return self._store_dir + + def save(self, creds: Credentials, name: str = None, + chain: List[Credentials] = None, + single_file: bool = False) -> None: + name = name if name is not None else creds.name + cert_file = self.get_cert_file(name=name, key_type=creds.key_type) + pkey_file = self.get_pkey_file(name=name, key_type=creds.key_type) + if single_file: + pkey_file = None + with open(cert_file, "wb") as fd: + fd.write(creds.cert_pem) + if chain: + for c in chain: + fd.write(c.cert_pem) + if pkey_file is None: + fd.write(creds.pkey_pem) + if pkey_file is not None: + with open(pkey_file, "wb") as fd: + fd.write(creds.pkey_pem) + creds.set_files(cert_file, pkey_file) + self._add_credentials(name, creds) + + def save_chain(self, creds: Credentials, infix: str, with_root=False): + name = creds.name + chain = [creds] + while creds.issuer is not None: + creds = creds.issuer + chain.append(creds) + if not with_root and len(chain) > 1: + chain = chain[:-1] + chain_file = os.path.join(self._store_dir, f'{name}-{infix}.pem') + with open(chain_file, "wb") as fd: + for c in chain: + fd.write(c.cert_pem) + + def _add_credentials(self, name: str, creds: Credentials): + if name not in self._creds_by_name: + self._creds_by_name[name] = [] + self._creds_by_name[name].append(creds) + + def get_credentials_for_name(self, name) -> List[Credentials]: + return self._creds_by_name[name] if name in self._creds_by_name else [] + + def get_cert_file(self, name: str, key_type=None) -> str: + key_infix = ".{0}".format(key_type) if key_type is not None else "" + return os.path.join(self._store_dir, f'{name}{key_infix}.cert.pem') + + def get_pkey_file(self, name: str, key_type=None) -> str: + key_infix = ".{0}".format(key_type) if key_type is not None else "" + return os.path.join(self._store_dir, f'{name}{key_infix}.pkey.pem') + + def load_pem_cert(self, fpath: str) -> x509.Certificate: + with open(fpath) as fd: + return x509.load_pem_x509_certificate("".join(fd.readlines()).encode()) + + def load_pem_pkey(self, fpath: str): + with open(fpath) as fd: + return load_pem_private_key("".join(fd.readlines()).encode(), password=None) + + def load_credentials(self, name: str, key_type=None, single_file: bool = False, issuer: Credentials = None): + cert_file = self.get_cert_file(name=name, key_type=key_type) + pkey_file = cert_file if single_file else self.get_pkey_file(name=name, key_type=key_type) + if os.path.isfile(cert_file) and os.path.isfile(pkey_file): + cert = self.load_pem_cert(cert_file) + pkey = self.load_pem_pkey(pkey_file) + creds = Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) + creds.set_store(self) + creds.set_files(cert_file, pkey_file) + self._add_credentials(name, creds) + return creds + return None + + +class Ngtcp2TestCA: + + @classmethod + def create_root(cls, name: str, store_dir: str, key_type: str = "rsa2048") -> Credentials: + store = CertStore(fpath=store_dir) + creds = store.load_credentials(name="ca", key_type=key_type, issuer=None) + if creds is None: + creds = Ngtcp2TestCA._make_ca_credentials(name=name, key_type=key_type) + store.save(creds, name="ca") + creds.set_store(store) + return creds + + @staticmethod + def create_credentials(spec: CertificateSpec, issuer: Credentials, key_type: Any, + valid_from: timedelta = timedelta(days=-1), + valid_to: timedelta = timedelta(days=89), + ) -> Credentials: + """Create a certificate signed by this CA for the given domains. + :returns: the certificate and private key PEM file paths + """ + if spec.domains and len(spec.domains): + creds = Ngtcp2TestCA._make_server_credentials(name=spec.name, domains=spec.domains, + issuer=issuer, valid_from=valid_from, + valid_to=valid_to, key_type=key_type) + elif spec.client: + creds = Ngtcp2TestCA._make_client_credentials(name=spec.name, issuer=issuer, + email=spec.email, valid_from=valid_from, + valid_to=valid_to, key_type=key_type) + elif spec.name: + creds = Ngtcp2TestCA._make_ca_credentials(name=spec.name, issuer=issuer, + valid_from=valid_from, valid_to=valid_to, + key_type=key_type) + else: + raise Exception(f"unrecognized certificate specification: {spec}") + return creds + + @staticmethod + def _make_x509_name(org_name: str = None, common_name: str = None, parent: x509.Name = None) -> x509.Name: + name_pieces = [] + if org_name: + oid = NameOID.ORGANIZATIONAL_UNIT_NAME if parent else NameOID.ORGANIZATION_NAME + name_pieces.append(x509.NameAttribute(oid, org_name)) + elif common_name: + name_pieces.append(x509.NameAttribute(NameOID.COMMON_NAME, common_name)) + if parent: + name_pieces.extend([rdn for rdn in parent]) + return x509.Name(name_pieces) + + @staticmethod + def _make_csr( + subject: x509.Name, + pkey: Any, + issuer_subject: Optional[Credentials], + valid_from_delta: timedelta = None, + valid_until_delta: timedelta = None + ): + pubkey = pkey.public_key() + issuer_subject = issuer_subject if issuer_subject is not None else subject + + valid_from = datetime.now() + if valid_until_delta is not None: + valid_from += valid_from_delta + valid_until = datetime.now() + if valid_until_delta is not None: + valid_until += valid_until_delta + + return ( + x509.CertificateBuilder() + .subject_name(subject) + .issuer_name(issuer_subject) + .public_key(pubkey) + .not_valid_before(valid_from) + .not_valid_after(valid_until) + .serial_number(x509.random_serial_number()) + .add_extension( + x509.SubjectKeyIdentifier.from_public_key(pubkey), + critical=False, + ) + ) + + @staticmethod + def _add_ca_usages(csr: Any) -> Any: + return csr.add_extension( + x509.BasicConstraints(ca=True, path_length=9), + critical=True, + ).add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=True, + encipher_only=False, + decipher_only=False), + critical=True + ).add_extension( + x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ]), + critical=True + ) + + @staticmethod + def _add_leaf_usages(csr: Any, domains: List[str], issuer: Credentials) -> Any: + return csr.add_extension( + x509.BasicConstraints(ca=False, path_length=None), + critical=True, + ).add_extension( + x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( + issuer.certificate.extensions.get_extension_for_class( + x509.SubjectKeyIdentifier).value), + critical=False + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(domain) for domain in domains]), + critical=True, + ).add_extension( + x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.SERVER_AUTH, + ]), + critical=True + ) + + @staticmethod + def _add_client_usages(csr: Any, issuer: Credentials, rfc82name: str = None) -> Any: + cert = csr.add_extension( + x509.BasicConstraints(ca=False, path_length=None), + critical=True, + ).add_extension( + x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( + issuer.certificate.extensions.get_extension_for_class( + x509.SubjectKeyIdentifier).value), + critical=False + ) + if rfc82name: + cert.add_extension( + x509.SubjectAlternativeName([x509.RFC822Name(rfc82name)]), + critical=True, + ) + cert.add_extension( + x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.CLIENT_AUTH, + ]), + critical=True + ) + return cert + + @staticmethod + def _make_ca_credentials(name, key_type: Any, + issuer: Credentials = None, + valid_from: timedelta = timedelta(days=-1), + valid_to: timedelta = timedelta(days=89), + ) -> Credentials: + pkey = _private_key(key_type=key_type) + if issuer is not None: + issuer_subject = issuer.certificate.subject + issuer_key = issuer.private_key + else: + issuer_subject = None + issuer_key = pkey + subject = Ngtcp2TestCA._make_x509_name(org_name=name, parent=issuer.subject if issuer else None) + csr = Ngtcp2TestCA._make_csr(subject=subject, + issuer_subject=issuer_subject, pkey=pkey, + valid_from_delta=valid_from, valid_until_delta=valid_to) + csr = Ngtcp2TestCA._add_ca_usages(csr) + cert = csr.sign(private_key=issuer_key, + algorithm=hashes.SHA256(), + backend=default_backend()) + return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) + + @staticmethod + def _make_server_credentials(name: str, domains: List[str], issuer: Credentials, + key_type: Any, + valid_from: timedelta = timedelta(days=-1), + valid_to: timedelta = timedelta(days=89), + ) -> Credentials: + name = name + pkey = _private_key(key_type=key_type) + subject = Ngtcp2TestCA._make_x509_name(common_name=name, parent=issuer.subject) + csr = Ngtcp2TestCA._make_csr(subject=subject, + issuer_subject=issuer.certificate.subject, pkey=pkey, + valid_from_delta=valid_from, valid_until_delta=valid_to) + csr = Ngtcp2TestCA._add_leaf_usages(csr, domains=domains, issuer=issuer) + cert = csr.sign(private_key=issuer.private_key, + algorithm=hashes.SHA256(), + backend=default_backend()) + return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) + + @staticmethod + def _make_client_credentials(name: str, + issuer: Credentials, email: Optional[str], + key_type: Any, + valid_from: timedelta = timedelta(days=-1), + valid_to: timedelta = timedelta(days=89), + ) -> Credentials: + pkey = _private_key(key_type=key_type) + subject = Ngtcp2TestCA._make_x509_name(common_name=name, parent=issuer.subject) + csr = Ngtcp2TestCA._make_csr(subject=subject, + issuer_subject=issuer.certificate.subject, pkey=pkey, + valid_from_delta=valid_from, valid_until_delta=valid_to) + csr = Ngtcp2TestCA._add_client_usages(csr, issuer=issuer, rfc82name=email) + cert = csr.sign(private_key=issuer.private_key, + algorithm=hashes.SHA256(), + backend=default_backend()) + return Credentials(name=name, cert=cert, pkey=pkey, issuer=issuer) diff --git a/examples/tests/ngtcp2test/client.py b/examples/tests/ngtcp2test/client.py new file mode 100644 index 0000000..2676343 --- /dev/null +++ b/examples/tests/ngtcp2test/client.py @@ -0,0 +1,187 @@ +import logging +import os +import re +import subprocess +from typing import List + +import pytest + +from .server import ExampleServer, ServerRun +from .certs import Credentials +from .tls import HandShake, HSRecord +from .env import Env, CryptoLib +from .log import LogFile, HexDumpScanner + + +log = logging.getLogger(__name__) + + +class ClientRun: + + def __init__(self, env: Env, returncode, logfile: LogFile, srun: ServerRun): + self.env = env + self.returncode = returncode + self.logfile = logfile + self.log_lines = logfile.get_recent() + self._data_recs = None + self._hs_recs = None + self._srun = srun + if self.env.verbose > 1: + log.debug(f'read {len(self.log_lines)} lines from {logfile.path}') + + @property + def handshake(self) -> List[HSRecord]: + if self._data_recs is None: + crypto_line = re.compile(r'Ordered CRYPTO data in \S+ crypto level') + scanner = HexDumpScanner(source=self.log_lines, + leading_regex=crypto_line) + self._data_recs = [data for data in scanner] + if self.env.verbose > 1: + log.debug(f'detected {len(self._data_recs)} crypto hexdumps ' + f'in {self.logfile.path}') + if self._hs_recs is None: + self._hs_recs = [hrec for hrec in HandShake(source=self._data_recs, + verbose=self.env.verbose)] + if self.env.verbose > 1: + log.debug(f'detected {len(self._hs_recs)} crypto ' + f'records in {self.logfile.path}') + return self._hs_recs + + @property + def hs_stripe(self) -> str: + return ":".join([hrec.name for hrec in self.handshake]) + + @property + def early_data_rejected(self) -> bool: + for l in self.log_lines: + if re.match(r'^Early data was rejected by server.*', l): + return True + return False + + @property + def server(self) -> ServerRun: + return self._srun + + def norm_exp(self, c_hs, s_hs, allow_hello_retry=True): + if allow_hello_retry and self.hs_stripe.startswith('HelloRetryRequest:'): + c_hs = "HelloRetryRequest:" + c_hs + s_hs = "ClientHello:" + s_hs + return c_hs, s_hs + + def _assert_hs(self, c_hs, s_hs): + if not self.hs_stripe.startswith(c_hs): + # what happened? + if self.hs_stripe == '': + # server send nothing + if self.server.hs_stripe == '': + # client send nothing + pytest.fail(f'client did not send a ClientHello"') + else: + # client send sth, but server did not respond + pytest.fail(f'server did not respond to ClientHello: ' + f'{self.server.handshake[0].to_text()}"') + else: + pytest.fail(f'Expected "{c_hs}", got "{self.hs_stripe}"') + assert self.server.hs_stripe == s_hs, \ + f'Expected "{s_hs}", got "{self.server.hs_stripe}"\n' + + def assert_non_resume_handshake(self, allow_hello_retry=True): + # for client/server where KEY_SHARE do not match, the hello is retried + c_hs, s_hs = self.norm_exp( + "ServerHello:EncryptedExtensions:Certificate:CertificateVerify:Finished", + "ClientHello:Finished", allow_hello_retry=allow_hello_retry) + self._assert_hs(c_hs, s_hs) + + def assert_resume_handshake(self): + # for client/server where KEY_SHARE do not match, the hello is retried + c_hs, s_hs = self.norm_exp("ServerHello:EncryptedExtensions:Finished", + "ClientHello:Finished") + self._assert_hs(c_hs, s_hs) + + def assert_verify_null_handshake(self): + c_hs, s_hs = self.norm_exp( + "ServerHello:EncryptedExtensions:CertificateRequest:Certificate:CertificateVerify:Finished", + "ClientHello:Certificate:Finished") + self._assert_hs(c_hs, s_hs) + + def assert_verify_cert_handshake(self): + c_hs, s_hs = self.norm_exp( + "ServerHello:EncryptedExtensions:CertificateRequest:Certificate:CertificateVerify:Finished", + "ClientHello:Certificate:CertificateVerify:Finished") + self._assert_hs(c_hs, s_hs) + + +class ExampleClient: + + def __init__(self, env: Env, crypto_lib: str): + self.env = env + self._crypto_lib = crypto_lib + self._path = env.client_path(self._crypto_lib) + self._log_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.log' + self._qlog_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.qlog' + self._session_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.session' + self._tp_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.tp' + self._data_path = f'{self.env.gen_dir}/{self._crypto_lib}-client.data' + + @property + def path(self): + return self._path + + @property + def crypto_lib(self): + return self._crypto_lib + + @property + def uses_cipher_config(self): + return CryptoLib.uses_cipher_config(self.crypto_lib) + + def supports_cipher(self, cipher): + return CryptoLib.supports_cipher(self.crypto_lib, cipher) + + def exists(self): + return os.path.isfile(self.path) + + def clear_session(self): + if os.path.isfile(self._session_path): + os.remove(self._session_path) + if os.path.isfile(self._tp_path): + os.remove(self._tp_path) + + def http_get(self, server: ExampleServer, url: str, extra_args: List[str] = None, + use_session=False, data=None, + credentials: Credentials = None, + ciphers: str = None): + args = [ + self.path, '--exit-on-all-streams-close', + f'--qlog-file={self._qlog_path}' + ] + if use_session: + args.append(f'--session-file={self._session_path}') + args.append(f'--tp-file={self._tp_path}') + if data is not None: + with open(self._data_path, 'w') as fd: + fd.write(data) + args.append(f'--data={self._data_path}') + if ciphers is not None: + ciphers = CryptoLib.adjust_ciphers(self.crypto_lib, ciphers) + args.append(f'--ciphers={ciphers}') + if credentials is not None: + args.append(f'--key={credentials.pkey_file}') + args.append(f'--cert={credentials.cert_file}') + if extra_args is not None: + args.extend(extra_args) + args.extend([ + 'localhost', str(self.env.examples_port), + url + ]) + if os.path.isfile(self._qlog_path): + os.remove(self._qlog_path) + with open(self._log_path, 'w') as log_file: + logfile = LogFile(path=self._log_path) + server.log.advance() + process = subprocess.Popen(args=args, text=True, + stdout=log_file, stderr=log_file) + process.wait() + return ClientRun(env=self.env, returncode=process.returncode, + logfile=logfile, srun=server.get_run()) + diff --git a/examples/tests/ngtcp2test/env.py b/examples/tests/ngtcp2test/env.py new file mode 100644 index 0000000..9699d55 --- /dev/null +++ b/examples/tests/ngtcp2test/env.py @@ -0,0 +1,191 @@ +import logging +import os +from configparser import ConfigParser, ExtendedInterpolation +from typing import Dict, Optional + +from .certs import CertificateSpec, Ngtcp2TestCA, Credentials + +log = logging.getLogger(__name__) + + +class CryptoLib: + + IGNORES_CIPHER_CONFIG = [ + 'picotls', 'boringssl' + ] + UNSUPPORTED_CIPHERS = { + 'wolfssl': [ + 'TLS_AES_128_CCM_SHA256', # no plans to + ], + 'picotls': [ + 'TLS_AES_128_CCM_SHA256', # no plans to + ], + 'boringssl': [ + 'TLS_AES_128_CCM_SHA256', # no plans to + ] + } + GNUTLS_CIPHERS = { + 'TLS_AES_128_GCM_SHA256': 'AES-128-GCM', + 'TLS_AES_256_GCM_SHA384': 'AES-256-GCM', + 'TLS_CHACHA20_POLY1305_SHA256': 'CHACHA20-POLY1305', + 'TLS_AES_128_CCM_SHA256': 'AES-128-CCM', + } + + @classmethod + def uses_cipher_config(cls, crypto_lib): + return crypto_lib not in cls.IGNORES_CIPHER_CONFIG + + @classmethod + def supports_cipher(cls, crypto_lib, cipher): + return crypto_lib not in cls.UNSUPPORTED_CIPHERS or \ + cipher not in cls.UNSUPPORTED_CIPHERS[crypto_lib] + + @classmethod + def adjust_ciphers(cls, crypto_lib, ciphers: str) -> str: + if crypto_lib == 'gnutls': + gciphers = "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL" + for cipher in ciphers.split(':'): + gciphers += f':+{cls.GNUTLS_CIPHERS[cipher]}' + return gciphers + return ciphers + + +def init_config_from(conf_path): + if os.path.isfile(conf_path): + config = ConfigParser(interpolation=ExtendedInterpolation()) + config.read(conf_path) + return config + return None + + +TESTS_PATH = os.path.dirname(os.path.dirname(__file__)) +EXAMPLES_PATH = os.path.dirname(TESTS_PATH) +DEF_CONFIG = init_config_from(os.path.join(TESTS_PATH, 'config.ini')) + + +class Env: + + @classmethod + def get_crypto_libs(cls, configurable_ciphers=None): + names = [name for name in DEF_CONFIG['examples'] + if DEF_CONFIG['examples'][name] == 'yes'] + if configurable_ciphers is not None: + names = [n for n in names if CryptoLib.uses_cipher_config(n)] + return names + + def __init__(self, examples_dir=None, tests_dir=None, config=None, + pytestconfig=None): + self._verbose = pytestconfig.option.verbose if pytestconfig is not None else 0 + self._examples_dir = examples_dir if examples_dir is not None else EXAMPLES_PATH + self._tests_dir = examples_dir if tests_dir is not None else TESTS_PATH + self._gen_dir = os.path.join(self._tests_dir, 'gen') + self.config = config if config is not None else DEF_CONFIG + self._version = self.config['ngtcp2']['version'] + self._crypto_libs = [name for name in self.config['examples'] + if self.config['examples'][name] == 'yes'] + self._clients = [self.config['clients'][lib] for lib in self._crypto_libs + if lib in self.config['clients']] + self._servers = [self.config['servers'][lib] for lib in self._crypto_libs + if lib in self.config['servers']] + self._examples_pem = { + 'key': 'xxx', + 'cert': 'xxx', + } + self._htdocs_dir = os.path.join(self._gen_dir, 'htdocs') + self._tld = 'tests.ngtcp2.nghttp2.org' + self._example_domain = f"one.{self._tld}" + self._ca = None + self._cert_specs = [ + CertificateSpec(domains=[self._example_domain], key_type='rsa2048'), + CertificateSpec(name="clientsX", sub_specs=[ + CertificateSpec(name="user1", client=True), + ]), + ] + + def issue_certs(self): + if self._ca is None: + self._ca = Ngtcp2TestCA.create_root(name=self._tld, + store_dir=os.path.join(self.gen_dir, 'ca'), + key_type="rsa2048") + self._ca.issue_certs(self._cert_specs) + + def setup(self): + os.makedirs(self._gen_dir, exist_ok=True) + os.makedirs(self._htdocs_dir, exist_ok=True) + self.issue_certs() + + def get_server_credentials(self) -> Optional[Credentials]: + creds = self.ca.get_credentials_for_name(self._example_domain) + if len(creds) > 0: + return creds[0] + return None + + @property + def verbose(self) -> int: + return self._verbose + + @property + def version(self) -> str: + return self._version + + @property + def gen_dir(self) -> str: + return self._gen_dir + + @property + def ca(self): + return self._ca + + @property + def htdocs_dir(self) -> str: + return self._htdocs_dir + + @property + def example_domain(self) -> str: + return self._example_domain + + @property + def examples_dir(self) -> str: + return self._examples_dir + + @property + def examples_port(self) -> int: + return int(self.config['examples']['port']) + + @property + def examples_pem(self) -> Dict[str, str]: + return self._examples_pem + + @property + def crypto_libs(self): + return self._crypto_libs + + @property + def clients(self): + return self._clients + + @property + def servers(self): + return self._servers + + def client_name(self, crypto_lib): + if crypto_lib in self.config['clients']: + return self.config['clients'][crypto_lib] + return None + + def client_path(self, crypto_lib): + cname = self.client_name(crypto_lib) + if cname is not None: + return os.path.join(self.examples_dir, cname) + return None + + def server_name(self, crypto_lib): + if crypto_lib in self.config['servers']: + return self.config['servers'][crypto_lib] + return None + + def server_path(self, crypto_lib): + sname = self.server_name(crypto_lib) + if sname is not None: + return os.path.join(self.examples_dir, sname) + return None diff --git a/examples/tests/ngtcp2test/log.py b/examples/tests/ngtcp2test/log.py new file mode 100644 index 0000000..9e8f399 --- /dev/null +++ b/examples/tests/ngtcp2test/log.py @@ -0,0 +1,101 @@ +import binascii +import os +import re +import sys +import time +from datetime import timedelta, datetime +from io import SEEK_END +from typing import List + + +class LogFile: + + def __init__(self, path: str): + self._path = path + self._start_pos = 0 + self._last_pos = self._start_pos + + @property + def path(self) -> str: + return self._path + + def reset(self): + self._start_pos = 0 + self._last_pos = self._start_pos + + def advance(self) -> None: + if os.path.isfile(self._path): + with open(self._path) as fd: + self._start_pos = fd.seek(0, SEEK_END) + + def get_recent(self, advance=True) -> List[str]: + lines = [] + if os.path.isfile(self._path): + with open(self._path) as fd: + fd.seek(self._last_pos, os.SEEK_SET) + for line in fd: + lines.append(line) + if advance: + self._last_pos = fd.tell() + return lines + + def scan_recent(self, pattern: re, timeout=10) -> bool: + if not os.path.isfile(self.path): + return False + with open(self.path) as fd: + end = datetime.now() + timedelta(seconds=timeout) + while True: + fd.seek(self._last_pos, os.SEEK_SET) + for line in fd: + if pattern.match(line): + return True + if datetime.now() > end: + raise TimeoutError(f"pattern not found in error log after {timeout} seconds") + time.sleep(.1) + return False + + +class HexDumpScanner: + + def __init__(self, source, leading_regex=None): + self._source = source + self._leading_regex = leading_regex + + def __iter__(self): + data = b'' + offset = 0 if self._leading_regex is None else -1 + idx = 0 + for l in self._source: + if offset == -1: + pass + elif offset == 0: + # possible start of a hex dump + m = re.match(r'^\s*0+(\s+-)?((\s+[0-9a-f]{2}){1,16})(\s+.*)$', + l, re.IGNORECASE) + if m: + data = binascii.unhexlify(re.sub(r'\s+', '', m.group(2))) + offset = 16 + idx = 1 + continue + else: + # possible continuation of a hexdump + m = re.match(r'^\s*([0-9a-f]+)(\s+-)?((\s+[0-9a-f]{2}){1,16})' + r'(\s+.*)$', l, re.IGNORECASE) + if m: + loffset = int(m.group(1), 16) + if loffset == offset or loffset == idx: + data += binascii.unhexlify(re.sub(r'\s+', '', + m.group(3))) + offset += 16 + idx += 1 + continue + else: + sys.stderr.write(f'wrong offset {loffset}, expected {offset} or {idx}\n') + # not a hexdump line, produce any collected data + if len(data) > 0: + yield data + data = b'' + offset = 0 if self._leading_regex is None \ + or self._leading_regex.match(l) else -1 + if len(data) > 0: + yield data diff --git a/examples/tests/ngtcp2test/server.py b/examples/tests/ngtcp2test/server.py new file mode 100644 index 0000000..9f4e9a0 --- /dev/null +++ b/examples/tests/ngtcp2test/server.py @@ -0,0 +1,137 @@ +import logging +import os +import re +import subprocess +import time +from datetime import datetime, timedelta +from threading import Thread + +from .tls import HandShake +from .env import Env, CryptoLib +from .log import LogFile, HexDumpScanner + + +log = logging.getLogger(__name__) + + +class ServerRun: + + def __init__(self, env: Env, logfile: LogFile): + self.env = env + self._logfile = logfile + self.log_lines = self._logfile.get_recent() + self._data_recs = None + self._hs_recs = None + if self.env.verbose > 1: + log.debug(f'read {len(self.log_lines)} lines from {logfile.path}') + + @property + def handshake(self): + if self._data_recs is None: + self._data_recs = [data for data in HexDumpScanner(source=self.log_lines)] + if self.env.verbose > 1: + log.debug(f'detected {len(self._data_recs)} hexdumps ' + f'in {self._logfile.path}') + if self._hs_recs is None: + self._hs_recs = [hrec for hrec in HandShake(source=self._data_recs, + verbose=self.env.verbose)] + if self.env.verbose > 1: + log.debug(f'detected {len(self._hs_recs)} crypto records ' + f'in {self._logfile.path}') + return self._hs_recs + + @property + def hs_stripe(self): + return ":".join([hrec.name for hrec in self.handshake]) + + +def monitor_proc(env: Env, proc): + _env = env + proc.wait() + + +class ExampleServer: + + def __init__(self, env: Env, crypto_lib: str, verify_client=False): + self.env = env + self._crypto_lib = crypto_lib + self._path = env.server_path(self._crypto_lib) + self._logpath = f'{self.env.gen_dir}/{self._crypto_lib}-server.log' + self._log = LogFile(path=self._logpath) + self._logfile = None + self._process = None + self._verify_client = verify_client + + @property + def path(self): + return self._path + + @property + def crypto_lib(self): + return self._crypto_lib + + @property + def uses_cipher_config(self): + return CryptoLib.uses_cipher_config(self.crypto_lib) + + def supports_cipher(self, cipher): + return CryptoLib.supports_cipher(self.crypto_lib, cipher) + + @property + def log(self): + return self._log + + def exists(self): + return os.path.isfile(self.path) + + def start(self): + if self._process is not None: + return False + creds = self.env.get_server_credentials() + assert creds + args = [ + self.path, + f'--htdocs={self.env.htdocs_dir}', + ] + if self._verify_client: + args.append('--verify-client') + args.extend([ + '*', str(self.env.examples_port), + creds.pkey_file, creds.cert_file + ]) + self._logfile = open(self._logpath, 'w') + self._process = subprocess.Popen(args=args, text=True, + stdout=self._logfile, stderr=self._logfile) + t = Thread(target=monitor_proc, daemon=True, args=(self.env, self._process)) + t.start() + timeout = 5 + end = datetime.now() + timedelta(seconds=timeout) + while True: + if self._process.poll(): + return False + try: + if self.log.scan_recent(pattern=re.compile(r'^Using document root'), timeout=0.5): + break + except TimeoutError: + pass + if datetime.now() > end: + raise TimeoutError(f"pattern not found in error log after {timeout} seconds") + self.log.advance() + return True + + def stop(self): + if self._process: + self._process.terminate() + self._process = None + if self._logfile: + self._logfile.close() + self._logfile = None + return True + + def restart(self): + self.stop() + self._log.reset() + return self.start() + + def get_run(self) -> ServerRun: + return ServerRun(env=self.env, logfile=self.log) diff --git a/examples/tests/ngtcp2test/tls.py b/examples/tests/ngtcp2test/tls.py new file mode 100644 index 0000000..f9bce62 --- /dev/null +++ b/examples/tests/ngtcp2test/tls.py @@ -0,0 +1,983 @@ +import binascii +import logging +import sys +from collections.abc import Iterator +from typing import Dict, Any, Iterable + + +log = logging.getLogger(__name__) + + +class ParseError(Exception): + pass + +def _get_int(d, n): + if len(d) < n: + raise ParseError(f'get_int: {n} bytes needed, but data is {d}') + if n == 1: + dlen = d[0] + else: + dlen = int.from_bytes(d[0:n], byteorder='big') + return d[n:], dlen + + +def _get_field(d, dlen): + if dlen > 0: + if len(d) < dlen: + raise ParseError(f'field len={dlen}, but data len={len(d)}') + field = d[0:dlen] + return d[dlen:], field + return d, b'' + + +def _get_len_field(d, n): + d, dlen = _get_int(d, n) + return _get_field(d, dlen) + + +# d are bytes that start with a quic variable length integer +def _get_qint(d): + i = d[0] & 0xc0 + if i == 0: + return d[1:], int(d[0]) + elif i == 0x40: + ndata = bytearray(d[0:2]) + d = d[2:] + ndata[0] = ndata[0] & ~0xc0 + return d, int.from_bytes(ndata, byteorder='big') + elif i == 0x80: + ndata = bytearray(d[0:4]) + d = d[4:] + ndata[0] = ndata[0] & ~0xc0 + return d, int.from_bytes(ndata, byteorder='big') + else: + ndata = bytearray(d[0:8]) + d = d[8:] + ndata[0] = ndata[0] & ~0xc0 + return d, int.from_bytes(ndata, byteorder='big') + + +class TlsSupportedGroups: + NAME_BY_ID = { + 0: 'Reserved', + 1: 'sect163k1', + 2: 'sect163r1', + 3: 'sect163r2', + 4: 'sect193r1', + 5: 'sect193r2', + 6: 'sect233k1', + 7: 'sect233r1', + 8: 'sect239k1', + 9: 'sect283k1', + 10: 'sect283r1', + 11: 'sect409k1', + 12: 'sect409r1', + 13: 'sect571k1', + 14: 'sect571r1', + 15: 'secp160k1', + 16: 'secp160r1', + 17: 'secp160r2', + 18: 'secp192k1', + 19: 'secp192r1', + 20: 'secp224k1', + 21: 'secp224r1', + 22: 'secp256k1', + 23: 'secp256r1', + 24: 'secp384r1', + 25: 'secp521r1', + 26: 'brainpoolP256r1', + 27: 'brainpoolP384r1', + 28: 'brainpoolP512r1', + 29: 'x25519', + 30: 'x448', + 31: 'brainpoolP256r1tls13', + 32: 'brainpoolP384r1tls13', + 33: 'brainpoolP512r1tls13', + 34: 'GC256A', + 35: 'GC256B', + 36: 'GC256C', + 37: 'GC256D', + 38: 'GC512A', + 39: 'GC512B', + 40: 'GC512C', + 41: 'curveSM2', + } + + @classmethod + def name(cls, gid): + if gid in cls.NAME_BY_ID: + return f'{cls.NAME_BY_ID[gid]}(0x{gid:0x})' + return f'0x{gid:0x}' + + +class TlsSignatureScheme: + NAME_BY_ID = { + 0x0201: 'rsa_pkcs1_sha1', + 0x0202: 'Reserved', + 0x0203: 'ecdsa_sha1', + 0x0401: 'rsa_pkcs1_sha256', + 0x0403: 'ecdsa_secp256r1_sha256', + 0x0420: 'rsa_pkcs1_sha256_legacy', + 0x0501: 'rsa_pkcs1_sha384', + 0x0503: 'ecdsa_secp384r1_sha384', + 0x0520: 'rsa_pkcs1_sha384_legacy', + 0x0601: 'rsa_pkcs1_sha512', + 0x0603: 'ecdsa_secp521r1_sha512', + 0x0620: 'rsa_pkcs1_sha512_legacy', + 0x0704: 'eccsi_sha256', + 0x0705: 'iso_ibs1', + 0x0706: 'iso_ibs2', + 0x0707: 'iso_chinese_ibs', + 0x0708: 'sm2sig_sm3', + 0x0709: 'gostr34102012_256a', + 0x070A: 'gostr34102012_256b', + 0x070B: 'gostr34102012_256c', + 0x070C: 'gostr34102012_256d', + 0x070D: 'gostr34102012_512a', + 0x070E: 'gostr34102012_512b', + 0x070F: 'gostr34102012_512c', + 0x0804: 'rsa_pss_rsae_sha256', + 0x0805: 'rsa_pss_rsae_sha384', + 0x0806: 'rsa_pss_rsae_sha512', + 0x0807: 'ed25519', + 0x0808: 'ed448', + 0x0809: 'rsa_pss_pss_sha256', + 0x080A: 'rsa_pss_pss_sha384', + 0x080B: 'rsa_pss_pss_sha512', + 0x081A: 'ecdsa_brainpoolP256r1tls13_sha256', + 0x081B: 'ecdsa_brainpoolP384r1tls13_sha384', + 0x081C: 'ecdsa_brainpoolP512r1tls13_sha512', + } + + @classmethod + def name(cls, gid): + if gid in cls.NAME_BY_ID: + return f'{cls.NAME_BY_ID[gid]}(0x{gid:0x})' + return f'0x{gid:0x}' + + +class TlsCipherSuites: + NAME_BY_ID = { + 0x1301: 'TLS_AES_128_GCM_SHA256', + 0x1302: 'TLS_AES_256_GCM_SHA384', + 0x1303: 'TLS_CHACHA20_POLY1305_SHA256', + 0x1304: 'TLS_AES_128_CCM_SHA256', + 0x1305: 'TLS_AES_128_CCM_8_SHA256', + 0x00ff: 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV', + } + + @classmethod + def name(cls, cid): + if cid in cls.NAME_BY_ID: + return f'{cls.NAME_BY_ID[cid]}(0x{cid:0x})' + return f'Cipher(0x{cid:0x})' + + +class PskKeyExchangeMode: + NAME_BY_ID = { + 0x00: 'psk_ke', + 0x01: 'psk_dhe_ke', + } + + @classmethod + def name(cls, gid): + if gid in cls.NAME_BY_ID: + return f'{cls.NAME_BY_ID[gid]}(0x{gid:0x})' + return f'0x{gid:0x}' + + +class QuicTransportParam: + NAME_BY_ID = { + 0x00: 'original_destination_connection_id', + 0x01: 'max_idle_timeout', + 0x02: 'stateless_reset_token', + 0x03: 'max_udp_payload_size', + 0x04: 'initial_max_data', + 0x05: 'initial_max_stream_data_bidi_local', + 0x06: 'initial_max_stream_data_bidi_remote', + 0x07: 'initial_max_stream_data_uni', + 0x08: 'initial_max_streams_bidi', + 0x09: 'initial_max_streams_uni', + 0x0a: 'ack_delay_exponent', + 0x0b: 'max_ack_delay', + 0x0c: 'disable_active_migration', + 0x0d: 'preferred_address', + 0x0e: 'active_connection_id_limit', + 0x0f: 'initial_source_connection_id', + 0x10: 'retry_source_connection_id', + } + TYPE_BY_ID = { + 0x00: bytes, + 0x01: int, + 0x02: bytes, + 0x03: int, + 0x04: int, + 0x05: int, + 0x06: int, + 0x07: int, + 0x08: int, + 0x09: int, + 0x0a: int, + 0x0b: int, + 0x0c: int, + 0x0d: bytes, + 0x0e: int, + 0x0f: bytes, + 0x10: bytes, + } + + @classmethod + def name(cls, cid): + if cid in cls.NAME_BY_ID: + return f'{cls.NAME_BY_ID[cid]}(0x{cid:0x})' + return f'QuicTP(0x{cid:0x})' + + @classmethod + def is_qint(cls, cid): + if cid in cls.TYPE_BY_ID: + return cls.TYPE_BY_ID[cid] == int + return False + + +class Extension: + + def __init__(self, eid, name, edata, hsid): + self._eid = eid + self._name = name + self._edata = edata + self._hsid = hsid + + @property + def data(self): + return self._edata + + @property + def hsid(self): + return self._hsid + + def to_json(self): + jdata = { + 'id': self._eid, + 'name': self._name, + } + if len(self.data) > 0: + jdata['data'] = binascii.hexlify(self.data).decode() + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + s = f'{ind}{self._name}(0x{self._eid:0x})' + if len(self._edata): + s += f'\n{ind} data({len(self._edata)}): ' \ + f'{binascii.hexlify(self._edata).decode()}' + return s + + +class ExtSupportedGroups(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + d = edata + self._groups = [] + while len(d) > 0: + d, gid = _get_int(d, 2) + self._groups.append(gid) + + def to_json(self): + jdata = { + 'id': self._eid, + 'name': self._name, + } + if len(self._groups): + jdata['groups'] = [TlsSupportedGroups.name(gid) + for gid in self._groups] + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + gnames = [TlsSupportedGroups.name(gid) for gid in self._groups] + s = f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(gnames)}' + return s + + +class ExtKeyShare(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + d = self.data + self._keys = [] + self._group = None + self._pubkey = None + if self.hsid == 2: # ServerHello + # single key share (group, pubkey) + d, self._group = _get_int(d, 2) + d, self._pubkey = _get_len_field(d, 2) + elif self.hsid == 6: # HelloRetryRequest + assert len(d) == 2 + d, self._group = _get_int(d, 2) + else: + # list if key shares (group, pubkey) + d, shares = _get_len_field(d, 2) + while len(shares) > 0: + shares, group = _get_int(shares, 2) + shares, pubkey = _get_len_field(shares, 2) + self._keys.append({ + 'group': TlsSupportedGroups.name(group), + 'pubkey': binascii.hexlify(pubkey).decode() + }) + + def to_json(self): + jdata = super().to_json() + if self._group is not None: + jdata['group'] = TlsSupportedGroups.name(self._group) + if self._pubkey is not None: + jdata['pubkey'] = binascii.hexlify(self._pubkey).decode() + if len(self._keys) > 0: + jdata['keys'] = self._keys + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + s = f'{ind}{self._name}(0x{self._eid:0x})' + if self._group is not None: + s += f'\n{ind} group: {TlsSupportedGroups.name(self._group)}' + if self._pubkey is not None: + s += f'\n{ind} pubkey: {binascii.hexlify(self._pubkey).decode()}' + if len(self._keys) > 0: + for idx, key in enumerate(self._keys): + s += f'\n{ind} {idx}: {key["group"]}, {key["pubkey"]}' + return s + + +class ExtSNI(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + d = self.data + self._indicators = [] + while len(d) > 0: + d, entry = _get_len_field(d, 2) + entry, stype = _get_int(entry, 1) + entry, sname = _get_len_field(entry, 2) + self._indicators.append({ + 'type': stype, + 'name': sname.decode() + }) + + def to_json(self): + jdata = super().to_json() + for i in self._indicators: + if i['type'] == 0: + jdata['host_name'] = i['name'] + else: + jdata[f'0x{i["type"]}'] = i['name'] + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + s = f'{ind}{self._name}(0x{self._eid:0x})' + if len(self._indicators) == 1 and self._indicators[0]['type'] == 0: + s += f': {self._indicators[0]["name"]}' + else: + for i in self._indicators: + ikey = 'host_name' if i["type"] == 0 else f'type(0x{i["type"]:0x}' + s += f'\n{ind} {ikey}: {i["name"]}' + return s + + +class ExtALPN(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + d = self.data + d, list_len = _get_int(d, 2) + self._protocols = [] + while len(d) > 0: + d, proto = _get_len_field(d, 1) + self._protocols.append(proto.decode()) + + def to_json(self): + jdata = super().to_json() + if len(self._protocols) == 1: + jdata['alpn'] = self._protocols[0] + else: + jdata['alpn'] = self._protocols + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._protocols)}' + + +class ExtEarlyData(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + self._max_size = None + d = self.data + if hsid == 4: # SessionTicket + assert len(d) == 4, f'expected 4, len is {len(d)} data={d}' + d, self._max_size = _get_int(d, 4) + else: + assert len(d) == 0 + + def to_json(self): + jdata = super().to_json() + if self._max_size is not None: + jdata['max_size'] = self._max_size + return jdata + + +class ExtSignatureAlgorithms(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + d = self.data + d, list_len = _get_int(d, 2) + self._algos = [] + while len(d) > 0: + d, algo = _get_int(d, 2) + self._algos.append(TlsSignatureScheme.name(algo)) + + def to_json(self): + jdata = super().to_json() + if len(self._algos) > 0: + jdata['algorithms'] = self._algos + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._algos)}' + + +class ExtPSKExchangeModes(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + d = self.data + d, list_len = _get_int(d, 1) + self._modes = [] + while len(d) > 0: + d, mode = _get_int(d, 1) + self._modes.append(PskKeyExchangeMode.name(mode)) + + def to_json(self): + jdata = super().to_json() + jdata['modes'] = self._modes + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._modes)}' + + +class ExtPreSharedKey(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + self._kid = None + self._identities = None + self._binders = None + d = self.data + if hsid == 1: # client hello + d, idata = _get_len_field(d, 2) + self._identities = [] + while len(idata): + idata, identity = _get_len_field(idata, 2) + idata, obfs_age = _get_int(idata, 4) + self._identities.append({ + 'id': binascii.hexlify(identity).decode(), + 'age': obfs_age, + }) + d, binders = _get_len_field(d, 2) + self._binders = [] + while len(binders) > 0: + binders, hmac = _get_len_field(binders, 1) + self._binders.append(binascii.hexlify(hmac).decode()) + assert len(d) == 0 + else: + d, self._kid = _get_int(d, 2) + + def to_json(self): + jdata = super().to_json() + if self.hsid == 1: + jdata['identities'] = self._identities + jdata['binders'] = self._binders + else: + jdata['identity'] = self._kid + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + s = f'{ind}{self._name}(0x{self._hsid:0x})' + if self.hsid == 1: + for idx, i in enumerate(self._identities): + s += f'\n{ind} {idx}: {i["id"]} ({i["age"]})' + s += f'\n{ind} binders: {self._binders}' + else: + s += f'\n{ind} identity: {self._kid}' + return s + + +class ExtSupportedVersions(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + d = self.data + self._versions = [] + if hsid == 1: # client hello + d, list_len = _get_int(d, 1) + while len(d) > 0: + d, version = _get_int(d, 2) + self._versions.append(f'0x{version:0x}') + else: + d, version = _get_int(d, 2) + self._versions.append(f'0x{version:0x}') + + def to_json(self): + jdata = super().to_json() + if len(self._versions) == 1: + jdata['version'] = self._versions[0] + else: + jdata['versions'] = self._versions + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return f'{ind}{self._name}(0x{self._eid:0x}): {", ".join(self._versions)}' + + +class ExtQuicTP(Extension): + + def __init__(self, eid, name, edata, hsid): + super().__init__(eid=eid, name=name, edata=edata, hsid=hsid) + d = self.data + self._params = [] + while len(d) > 0: + d, ptype = _get_qint(d) + d, plen = _get_qint(d) + d, pvalue = _get_field(d, plen) + if QuicTransportParam.is_qint(ptype): + _, pvalue = _get_qint(pvalue) + else: + pvalue = binascii.hexlify(pvalue).decode() + self._params.append({ + 'key': QuicTransportParam.name(ptype), + 'value': pvalue, + }) + + def to_json(self): + jdata = super().to_json() + jdata['params'] = self._params + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + s = f'{ind}{self._name}(0x{self._eid:0x})' + for p in self._params: + s += f'\n{ind} {p["key"]}: {p["value"]}' + return s + + +class TlsExtensions: + + EXT_TYPES = [ + (0x00, 'SNI', ExtSNI), + (0x01, 'MAX_FRAGMENT_LENGTH', Extension), + (0x03, 'TRUSTED_CA_KEYS', Extension), + (0x04, 'TRUNCATED_HMAC', Extension), + (0x05, 'OSCP_STATUS_REQUEST', Extension), + (0x0a, 'SUPPORTED_GROUPS', ExtSupportedGroups), + (0x0b, 'EC_POINT_FORMATS', Extension), + (0x0d, 'SIGNATURE_ALGORITHMS', ExtSignatureAlgorithms), + (0x0e, 'USE_SRTP', Extension), + (0x10, 'ALPN', ExtALPN), + (0x11, 'STATUS_REQUEST_V2', Extension), + (0x16, 'ENCRYPT_THEN_MAC', Extension), + (0x17, 'EXTENDED_MASTER_SECRET', Extension), + (0x23, 'SESSION_TICKET', Extension), + (0x29, 'PRE_SHARED_KEY', ExtPreSharedKey), + (0x2a, 'EARLY_DATA', ExtEarlyData), + (0x2b, 'SUPPORTED_VERSIONS', ExtSupportedVersions), + (0x2c, 'COOKIE', Extension), + (0x2d, 'PSK_KEY_EXCHANGE_MODES', ExtPSKExchangeModes), + (0x31, 'POST_HANDSHAKE_AUTH', Extension), + (0x32, 'SIGNATURE_ALGORITHMS_CERT', Extension), + (0x33, 'KEY_SHARE', ExtKeyShare), + (0x39, 'QUIC_TP_PARAMS', ExtQuicTP), + (0xff01, 'RENEGOTIATION_INFO', Extension), + (0xffa5, 'QUIC_TP_PARAMS_DRAFT', ExtQuicTP), + ] + NAME_BY_ID = {} + CLASS_BY_ID = {} + + @classmethod + def init(cls): + for (eid, name, ecls) in cls.EXT_TYPES: + cls.NAME_BY_ID[eid] = name + cls.CLASS_BY_ID[eid] = ecls + + @classmethod + def from_data(cls, hsid, data): + exts = [] + d = data + while len(d): + d, eid = _get_int(d, 2) + d, elen = _get_int(d, 2) + d, edata = _get_field(d, elen) + if eid in cls.NAME_BY_ID: + ename = cls.NAME_BY_ID[eid] + ecls = cls.CLASS_BY_ID[eid] + exts.append(ecls(eid=eid, name=ename, edata=edata, hsid=hsid)) + else: + exts.append(Extension(eid=eid, name=f'(0x{eid:0x})', + edata=edata, hsid=hsid)) + return exts + + +TlsExtensions.init() + + +class HSRecord: + + def __init__(self, hsid: int, name: str, data): + self._hsid = hsid + self._name = name + self._data = data + + @property + def hsid(self): + return self._hsid + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + @property + def data(self): + return self._data + + def __repr__(self): + return f'{self.name}[{binascii.hexlify(self._data).decode()}]' + + def to_json(self) -> Dict[str, Any]: + return { + 'name': self.name, + 'data': binascii.hexlify(self._data).decode(), + } + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return f'{ind}{self._name}\n'\ + f'{ind} id: 0x{self._hsid:0x}\n'\ + f'{ind} data({len(self._data)}): '\ + f'{binascii.hexlify(self._data).decode()}' + + +class ClientHello(HSRecord): + + def __init__(self, hsid: int, name: str, data): + super().__init__(hsid=hsid, name=name, data=data) + d = data + d, self._version = _get_int(d, 2) + d, self._random = _get_field(d, 32) + d, self._session_id = _get_len_field(d, 1) + self._ciphers = [] + d, ciphers = _get_len_field(d, 2) + while len(ciphers): + ciphers, cipher = _get_int(ciphers, 2) + self._ciphers.append(TlsCipherSuites.name(cipher)) + d, comps = _get_len_field(d, 1) + self._compressions = [int(c) for c in comps] + d, edata = _get_len_field(d, 2) + self._extensions = TlsExtensions.from_data(hsid, edata) + + def to_json(self): + jdata = super().to_json() + jdata['version'] = f'0x{self._version:0x}' + jdata['random'] = f'{binascii.hexlify(self._random).decode()}' + jdata['session_id'] = binascii.hexlify(self._session_id).decode() + jdata['ciphers'] = self._ciphers + jdata['compressions'] = self._compressions + jdata['extensions'] = [ext.to_json() for ext in self._extensions] + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return super().to_text(indent=indent) + '\n'\ + f'{ind} version: 0x{self._version:0x}\n'\ + f'{ind} random: {binascii.hexlify(self._random).decode()}\n' \ + f'{ind} session_id: {binascii.hexlify(self._session_id).decode()}\n' \ + f'{ind} ciphers: {", ".join(self._ciphers)}\n'\ + f'{ind} compressions: {self._compressions}\n'\ + f'{ind} extensions: \n' + '\n'.join( + [ext.to_text(indent=indent+4) for ext in self._extensions]) + + +class ServerHello(HSRecord): + + HELLO_RETRY_RANDOM = binascii.unhexlify( + 'CF21AD74E59A6111BE1D8C021E65B891C2A211167ABB8C5E079E09E2C8A8339C' + ) + + def __init__(self, hsid: int, name: str, data): + super().__init__(hsid=hsid, name=name, data=data) + d = data + d, self._version = _get_int(d, 2) + d, self._random = _get_field(d, 32) + if self._random == self.HELLO_RETRY_RANDOM: + self.name = 'HelloRetryRequest' + hsid = 6 + d, self._session_id = _get_len_field(d, 1) + d, cipher = _get_int(d, 2) + self._cipher = TlsCipherSuites.name(cipher) + d, self._compression = _get_int(d, 1) + d, edata = _get_len_field(d, 2) + self._extensions = TlsExtensions.from_data(hsid, edata) + + def to_json(self): + jdata = super().to_json() + jdata['version'] = f'0x{self._version:0x}' + jdata['random'] = f'{binascii.hexlify(self._random).decode()}' + jdata['session_id'] = binascii.hexlify(self._session_id).decode() + jdata['cipher'] = self._cipher + jdata['compression'] = int(self._compression) + jdata['extensions'] = [ext.to_json() for ext in self._extensions] + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return super().to_text(indent=indent) + '\n'\ + f'{ind} version: 0x{self._version:0x}\n'\ + f'{ind} random: {binascii.hexlify(self._random).decode()}\n' \ + f'{ind} session_id: {binascii.hexlify(self._session_id).decode()}\n' \ + f'{ind} cipher: {self._cipher}\n'\ + f'{ind} compression: {int(self._compression)}\n'\ + f'{ind} extensions: \n' + '\n'.join( + [ext.to_text(indent=indent+4) for ext in self._extensions]) + + +class EncryptedExtensions(HSRecord): + + def __init__(self, hsid: int, name: str, data): + super().__init__(hsid=hsid, name=name, data=data) + d = data + d, edata = _get_len_field(d, 2) + self._extensions = TlsExtensions.from_data(hsid, edata) + + def to_json(self): + jdata = super().to_json() + jdata['extensions'] = [ext.to_json() for ext in self._extensions] + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return super().to_text(indent=indent) + '\n'\ + f'{ind} extensions: \n' + '\n'.join( + [ext.to_text(indent=indent+4) for ext in self._extensions]) + + +class CertificateRequest(HSRecord): + + def __init__(self, hsid: int, name: str, data): + super().__init__(hsid=hsid, name=name, data=data) + d = data + d, self._context = _get_int(d, 1) + d, edata = _get_len_field(d, 2) + self._extensions = TlsExtensions.from_data(hsid, edata) + + def to_json(self): + jdata = super().to_json() + jdata['context'] = self._context + jdata['extensions'] = [ext.to_json() for ext in self._extensions] + return jdata + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return super().to_text(indent=indent) + '\n'\ + f'{ind} context: {self._context}\n'\ + f'{ind} extensions: \n' + '\n'.join( + [ext.to_text(indent=indent+4) for ext in self._extensions]) + + +class Certificate(HSRecord): + + def __init__(self, hsid: int, name: str, data): + super().__init__(hsid=hsid, name=name, data=data) + d = data + d, self._context = _get_int(d, 1) + d, clist = _get_len_field(d, 3) + self._cert_entries = [] + while len(clist) > 0: + clist, cert_data = _get_len_field(clist, 3) + clist, cert_exts = _get_len_field(clist, 2) + exts = TlsExtensions.from_data(hsid, cert_exts) + self._cert_entries.append({ + 'cert': binascii.hexlify(cert_data).decode(), + 'extensions': exts, + }) + + def to_json(self): + jdata = super().to_json() + jdata['context'] = self._context + jdata['certificate_list'] = [{ + 'cert': e['cert'], + 'extensions': [x.to_json() for x in e['extensions']], + } for e in self._cert_entries] + return jdata + + def _enxtry_text(self, e, indent: int = 0): + ind = ' ' * (indent + 2) + return f'{ind} cert: {e["cert"]}\n'\ + f'{ind} extensions: \n' + '\n'.join( + [x.to_text(indent=indent + 4) for x in e['extensions']]) + + def to_text(self, indent: int = 0): + ind = ' ' * (indent + 2) + return super().to_text(indent=indent) + '\n'\ + f'{ind} context: {self._context}\n'\ + f'{ind} certificate_list: \n' + '\n'.join( + [self._enxtry_text(e, indent+4) for e in self._cert_entries]) + + +class SessionTicket(HSRecord): + + def __init__(self, hsid: int, name: str, data): + super().__init__(hsid=hsid, name=name, data=data) + d = data + d, self._lifetime = _get_int(d, 4) + d, self._age = _get_int(d, 4) + d, self._nonce = _get_len_field(d, 1) + d, self._ticket = _get_len_field(d, 2) + d, edata = _get_len_field(d, 2) + self._extensions = TlsExtensions.from_data(hsid, edata) + + def to_json(self): + jdata = super().to_json() + jdata['lifetime'] = self._lifetime + jdata['age'] = self._age + jdata['nonce'] = binascii.hexlify(self._nonce).decode() + jdata['ticket'] = binascii.hexlify(self._ticket).decode() + jdata['extensions'] = [ext.to_json() for ext in self._extensions] + return jdata + + +class HSIterator(Iterator): + + def __init__(self, recs): + self._recs = recs + self._index = 0 + + def __iter__(self): + return self + + def __next__(self): + try: + result = self._recs[self._index] + except IndexError: + raise StopIteration + self._index += 1 + return result + + +class HandShake: + REC_TYPES = [ + (1, 'ClientHello', ClientHello), + (2, 'ServerHello', ServerHello), + (3, 'HelloVerifyRequest', HSRecord), + (4, 'SessionTicket', SessionTicket), + (5, 'EndOfEarlyData', HSRecord), + (6, 'HelloRetryRequest', ServerHello), + (8, 'EncryptedExtensions', EncryptedExtensions), + (11, 'Certificate', Certificate), + (12, 'ServerKeyExchange ', HSRecord), + (13, 'CertificateRequest', CertificateRequest), + (14, 'ServerHelloDone', HSRecord), + (15, 'CertificateVerify', HSRecord), + (16, 'ClientKeyExchange', HSRecord), + (20, 'Finished', HSRecord), + (22, 'CertificateStatus', HSRecord), + (24, 'KeyUpdate', HSRecord), + ] + RT_NAME_BY_ID = {} + RT_CLS_BY_ID = {} + + @classmethod + def _parse_rec(cls, data): + d, hsid = _get_int(data, 1) + if hsid not in cls.RT_CLS_BY_ID: + raise ParseError(f'unknown type {hsid}') + d, rec_len = _get_int(d, 3) + if rec_len > len(d): + # incomplete, need more data + return data, None + d, rec_data = _get_field(d, rec_len) + if hsid in cls.RT_CLS_BY_ID: + name = cls.RT_NAME_BY_ID[hsid] + rcls = cls.RT_CLS_BY_ID[hsid] + else: + name = f'CryptoRecord(0x{hsid:0x})' + rcls = HSRecord + return d, rcls(hsid=hsid, name=name, data=rec_data) + + @classmethod + def _parse(cls, source, strict=False, verbose: int = 0): + d = b'' + hsid = 0 + hsrecs = [] + if verbose > 0: + log.debug(f'scanning for handshake records') + blocks = [d for d in source] + while len(blocks) > 0: + try: + total_data = b''.join(blocks) + remain, r = cls._parse_rec(total_data) + if r is None: + # if we could not recognize a record, skip the first + # data block and try again + blocks = blocks[1:] + continue + hsrecs.append(r) + cons_len = len(total_data) - len(remain) + while cons_len > 0 and len(blocks) > 0: + if cons_len >= len(blocks[0]): + cons_len -= len(blocks[0]) + blocks = blocks[1:] + else: + blocks[0] = blocks[0][cons_len:] + cons_len = 0 + if verbose > 2: + log.debug(f'added record: {r.to_text()}') + except ParseError as err: + # if we could not recognize a record, skip the first + # data block and try again + blocks = blocks[1:] + if len(blocks) > 0 and strict: + raise Exception(f'possibly incomplete handshake record ' + f'id={hsid}, from raw={blocks}\n') + return hsrecs + + + + @classmethod + def init(cls): + for (hsid, name, rcls) in cls.REC_TYPES: + cls.RT_NAME_BY_ID[hsid] = name + cls.RT_CLS_BY_ID[hsid] = rcls + + def __init__(self, source: Iterable[bytes], strict: bool = False, + verbose: int = 0): + self._source = source + self._strict = strict + self._verbose = verbose + + def __iter__(self): + return HSIterator(recs=self._parse(self._source, strict=self._strict, + verbose=self._verbose)) + + +HandShake.init() diff --git a/examples/tests/test_01_handshake.py b/examples/tests/test_01_handshake.py new file mode 100644 index 0000000..f1a01d1 --- /dev/null +++ b/examples/tests/test_01_handshake.py @@ -0,0 +1,30 @@ +import pytest + +from .ngtcp2test import ExampleClient +from .ngtcp2test import ExampleServer +from .ngtcp2test import Env + + +@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0, + reason="no crypto lib examples configured") +class TestHandshake: + + @pytest.fixture(scope='class', params=Env.get_crypto_libs()) + def server(self, env, request) -> ExampleServer: + s = ExampleServer(env=env, crypto_lib=request.param) + assert s.exists(), f'server not found: {s.path}' + assert s.start() + yield s + s.stop() + + @pytest.fixture(scope='function', params=Env.get_crypto_libs()) + def client(self, env, request) -> ExampleClient: + client = ExampleClient(env=env, crypto_lib=request.param) + assert client.exists() + yield client + + def test_01_01_get(self, env: Env, server, client): + # run simple GET, no sessions, needs to give full handshake + cr = client.http_get(server, url=f'https://{env.example_domain}/') + assert cr.returncode == 0 + cr.assert_non_resume_handshake() diff --git a/examples/tests/test_02_resume.py b/examples/tests/test_02_resume.py new file mode 100644 index 0000000..3de1344 --- /dev/null +++ b/examples/tests/test_02_resume.py @@ -0,0 +1,46 @@ +import pytest + +from .ngtcp2test import ExampleClient +from .ngtcp2test import ExampleServer +from .ngtcp2test import Env + + +@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0, + reason="no crypto lib examples configured") +class TestResume: + + @pytest.fixture(scope='class', params=Env.get_crypto_libs()) + def server(self, env, request) -> ExampleServer: + s = ExampleServer(env=env, crypto_lib=request.param) + assert s.exists(), f'server not found: {s.path}' + assert s.start() + yield s + s.stop() + + @pytest.fixture(scope='function', params=Env.get_crypto_libs()) + def client(self, env, request) -> ExampleClient: + client = ExampleClient(env=env, crypto_lib=request.param) + assert client.exists() + yield client + + def test_02_01(self, env: Env, server, client): + # run GET with sessions but no early data, cleared first, then reused + client.clear_session() + cr = client.http_get(server, url=f'https://{env.example_domain}/', + use_session=True, + extra_args=['--disable-early-data']) + assert cr.returncode == 0 + cr.assert_non_resume_handshake() + # Now do this again and we expect a resumption, meaning no certificate + cr = client.http_get(server, url=f'https://{env.example_domain}/', + use_session=True, + extra_args=['--disable-early-data']) + assert cr.returncode == 0 + cr.assert_resume_handshake() + # restart the server, do it again + server.restart() + cr = client.http_get(server, url=f'https://{env.example_domain}/', + use_session=True, + extra_args=['--disable-early-data']) + assert cr.returncode == 0 + cr.assert_non_resume_handshake() diff --git a/examples/tests/test_03_earlydata.py b/examples/tests/test_03_earlydata.py new file mode 100644 index 0000000..a0170c3 --- /dev/null +++ b/examples/tests/test_03_earlydata.py @@ -0,0 +1,56 @@ +import pytest + +from .ngtcp2test import ExampleClient +from .ngtcp2test import ExampleServer +from .ngtcp2test import Env + + +@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0, + reason="no crypto lib examples configured") +class TestEarlyData: + + @pytest.fixture(scope='class', params=Env.get_crypto_libs()) + def server(self, env, request) -> ExampleServer: + s = ExampleServer(env=env, crypto_lib=request.param) + assert s.exists(), f'server not found: {s.path}' + assert s.start() + yield s + s.stop() + + @pytest.fixture(scope='function', params=Env.get_crypto_libs()) + def client(self, env, request) -> ExampleClient: + client = ExampleClient(env=env, crypto_lib=request.param) + assert client.exists() + yield client + + def test_03_01(self, env: Env, server, client): + # run GET with sessions, cleared first, without a session, early + # data will not even be attempted + client.clear_session() + edata = 'This is the early data. It is not much.' + cr = client.http_get(server, url=f'https://{env.example_domain}/', + use_session=True, data=edata) + assert cr.returncode == 0 + cr.assert_non_resume_handshake() + # resume session, early data is sent and accepted + cr = client.http_get(server, url=f'https://{env.example_domain}/', + use_session=True, data=edata) + assert cr.returncode == 0 + cr.assert_resume_handshake() + assert not cr.early_data_rejected + # restart the server, resume, early data is attempted but will not work + server.restart() + cr = client.http_get(server, url=f'https://{env.example_domain}/', + use_session=True, data=edata) + assert cr.returncode == 0 + assert cr.early_data_rejected + cr.assert_non_resume_handshake() + # restart again, sent data, but not as early data + server.restart() + cr = client.http_get(server, url=f'https://{env.example_domain}/', + use_session=True, data=edata, + extra_args=['--disable-early-data']) + assert cr.returncode == 0 + # we see no rejection, since it was not used + assert not cr.early_data_rejected + cr.assert_non_resume_handshake() diff --git a/examples/tests/test_04_clientcert.py b/examples/tests/test_04_clientcert.py new file mode 100644 index 0000000..bde1b18 --- /dev/null +++ b/examples/tests/test_04_clientcert.py @@ -0,0 +1,57 @@ +import pytest + +from .ngtcp2test import ExampleClient +from .ngtcp2test import ExampleServer +from .ngtcp2test import Env + + +@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0, + reason="no crypto lib examples configured") +class TestClientCert: + + @pytest.fixture(scope='class', params=Env.get_crypto_libs()) + def server(self, env, request) -> ExampleServer: + s = ExampleServer(env=env, crypto_lib=request.param, + verify_client=True) + assert s.exists(), f'server not found: {s.path}' + assert s.start() + yield s + s.stop() + + @pytest.fixture(scope='function', params=Env.get_crypto_libs()) + def client(self, env, request) -> ExampleClient: + client = ExampleClient(env=env, crypto_lib=request.param) + assert client.exists() + yield client + + def test_04_01(self, env: Env, server, client): + # run GET with a server requesting a cert, client has none to offer + cr = client.http_get(server, url=f'https://{env.example_domain}/') + assert cr.returncode == 0 + cr.assert_verify_null_handshake() + creqs = [r for r in cr.handshake if r.hsid == 13] # CertificateRequest + assert len(creqs) == 1 + creq = creqs[0].to_json() + certs = [r for r in cr.server.handshake if r.hsid == 11] # Certificate + assert len(certs) == 1 + crec = certs[0].to_json() + assert len(crec['certificate_list']) == 0 + assert creq['context'] == crec['context'] + # TODO: check that GET had no answer + + def test_04_02(self, env: Env, server, client): + # run GET with a server requesting a cert, client has cert to offer + credentials = env.ca.get_first("clientsX") + cr = client.http_get(server, url=f'https://{env.example_domain}/', + credentials=credentials) + assert cr.returncode == 0 + cr.assert_verify_cert_handshake() + creqs = [r for r in cr.handshake if r.hsid == 13] # CertificateRequest + assert len(creqs) == 1 + creq = creqs[0].to_json() + certs = [r for r in cr.server.handshake if r.hsid == 11] # Certificate + assert len(certs) == 1 + crec = certs[0].to_json() + assert len(crec['certificate_list']) == 1 + assert creq['context'] == crec['context'] + # TODO: check that GET indeed gave a response diff --git a/examples/tests/test_05_ciphers.py b/examples/tests/test_05_ciphers.py new file mode 100644 index 0000000..27f326e --- /dev/null +++ b/examples/tests/test_05_ciphers.py @@ -0,0 +1,46 @@ +import sys + +import pytest + +from .ngtcp2test import ExampleClient +from .ngtcp2test import ExampleServer +from .ngtcp2test import Env + + +@pytest.mark.skipif(condition=len(Env.get_crypto_libs()) == 0, + reason="no crypto lib examples configured") +class TestCiphers: + + @pytest.fixture(scope='class', params=Env.get_crypto_libs()) + def server(self, env, request) -> ExampleServer: + s = ExampleServer(env=env, crypto_lib=request.param) + assert s.exists(), f'server not found: {s.path}' + assert s.start() + yield s + s.stop() + + @pytest.fixture(scope='function', + params=Env.get_crypto_libs(configurable_ciphers=True)) + def client(self, env, request) -> ExampleClient: + client = ExampleClient(env=env, crypto_lib=request.param) + assert client.exists() + yield client + + @pytest.mark.parametrize('cipher', [ + 'TLS_AES_128_GCM_SHA256', + 'TLS_AES_256_GCM_SHA384', + 'TLS_CHACHA20_POLY1305_SHA256', + 'TLS_AES_128_CCM_SHA256', + ]) + def test_05_01_get(self, env: Env, server, client, cipher): + if not client.uses_cipher_config: + pytest.skip(f'client {client.crypto_lib} ignores cipher config\n') + # run simple GET, no sessions, needs to give full handshake + if not client.supports_cipher(cipher): + pytest.skip(f'client {client.crypto_lib} does not support {cipher}\n') + if not server.supports_cipher(cipher): + pytest.skip(f'server {server.crypto_lib} does not support {cipher}\n') + cr = client.http_get(server, url=f'https://{env.example_domain}/', + ciphers=cipher) + assert cr.returncode == 0 + cr.assert_non_resume_handshake() diff --git a/examples/tls_client_context.h b/examples/tls_client_context.h new file mode 100644 index 0000000..31e2d47 --- /dev/null +++ b/examples/tls_client_context.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_CONTEXT_H +#define TLS_CLIENT_CONTEXT_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#if defined(ENABLE_EXAMPLE_OPENSSL) && defined(WITH_EXAMPLE_OPENSSL) +# include "tls_client_context_openssl.h" +#endif // ENABLE_EXAMPLE_OPENSSL && WITH_EXAMPLE_OPENSSL + +#if defined(ENABLE_EXAMPLE_GNUTLS) && defined(WITH_EXAMPLE_GNUTLS) +# include "tls_client_context_gnutls.h" +#endif // ENABLE_EXAMPLE_GNUTLS && WITH_EXAMPLE_GNUTLS + +#if defined(ENABLE_EXAMPLE_BORINGSSL) && defined(WITH_EXAMPLE_BORINGSSL) +# include "tls_client_context_boringssl.h" +#endif // ENABLE_EXAMPLE_BORINGSSL && WITH_EXAMPLE_BORINGSSL + +#if defined(ENABLE_EXAMPLE_PICOTLS) && defined(WITH_EXAMPLE_PICOTLS) +# include "tls_client_context_picotls.h" +#endif // ENABLE_EXAMPLE_PICOTLS && WITH_EXAMPLE_PICOTLS + +#if defined(ENABLE_EXAMPLE_WOLFSSL) && defined(WITH_EXAMPLE_WOLFSSL) +# include "tls_client_context_wolfssl.h" +#endif // ENABLE_EXAMPLE_WOLFSSL && WITH_EXAMPLE_WOLFSSL + +#endif // TLS_CLIENT_CONTEXT_H diff --git a/examples/tls_client_context_boringssl.cc b/examples/tls_client_context_boringssl.cc new file mode 100644 index 0000000..bfdc525 --- /dev/null +++ b/examples/tls_client_context_boringssl.cc @@ -0,0 +1,126 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_context_boringssl.h" + +#include <iostream> +#include <fstream> + +#include <ngtcp2/ngtcp2_crypto_boringssl.h> + +#include <openssl/err.h> + +#include "client_base.h" +#include "template.h" + +extern Config config; + +TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {} + +TLSClientContext::~TLSClientContext() { + if (ssl_ctx_) { + SSL_CTX_free(ssl_ctx_); + } +} + +SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; } + +namespace { +int new_session_cb(SSL *ssl, SSL_SESSION *session) { + auto f = BIO_new_file(config.session_file, "w"); + if (f == nullptr) { + std::cerr << "Could not write TLS session in " << config.session_file + << std::endl; + return 0; + } + + if (!PEM_write_bio_SSL_SESSION(f, session)) { + std::cerr << "Unable to write TLS session to file" << std::endl; + } + + BIO_free(f); + + return 0; +} +} // namespace + +int TLSClientContext::init(const char *private_key_file, + const char *cert_file) { + ssl_ctx_ = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx_) { + std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + if (ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx_) != 0) { + std::cerr << "ngtcp2_crypto_boringssl_configure_client_context failed" + << std::endl; + return -1; + } + + SSL_CTX_set_default_verify_paths(ssl_ctx_); + + if (SSL_CTX_set1_curves_list(ssl_ctx_, config.groups) != 1) { + std::cerr << "SSL_CTX_set1_curves_list failed" << std::endl; + return -1; + } + + if (private_key_file && cert_file) { + if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, + SSL_FILETYPE_PEM) != 1) { + std::cerr << "SSL_CTX_use_PrivateKey_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) { + std::cerr << "SSL_CTX_use_certificate_chain_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + } + + if (config.session_file) { + SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb); + } + + return 0; +} + +extern std::ofstream keylog_file; + +namespace { +void keylog_callback(const SSL *ssl, const char *line) { + keylog_file.write(line, strlen(line)); + keylog_file.put('\n'); + keylog_file.flush(); +} +} // namespace + +void TLSClientContext::enable_keylog() { + SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); +} diff --git a/examples/tls_client_context_boringssl.h b/examples/tls_client_context_boringssl.h new file mode 100644 index 0000000..22b581a --- /dev/null +++ b/examples/tls_client_context_boringssl.h @@ -0,0 +1,49 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_CONTEXT_BORINGSSL_H +#define TLS_CLIENT_CONTEXT_BORINGSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <openssl/ssl.h> + +class TLSClientContext { +public: + TLSClientContext(); + ~TLSClientContext(); + + int init(const char *private_key_file, const char *cert_file); + + SSL_CTX *get_native_handle() const; + + void enable_keylog(); + +private: + SSL_CTX *ssl_ctx_; +}; + +#endif // TLS_CLIENT_CONTEXT_BORINGSSL_H diff --git a/examples/tls_client_context_gnutls.cc b/examples/tls_client_context_gnutls.cc new file mode 100644 index 0000000..1fa03a8 --- /dev/null +++ b/examples/tls_client_context_gnutls.cc @@ -0,0 +1,74 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_context_gnutls.h" + +#include <iostream> + +#include <ngtcp2/ngtcp2_crypto_gnutls.h> + +#include "client_base.h" +#include "template.h" + +// Based on https://github.com/ueno/ngtcp2-gnutls-examples + +extern Config config; + +TLSClientContext::TLSClientContext() : cred_{nullptr} {} + +TLSClientContext::~TLSClientContext() { + gnutls_certificate_free_credentials(cred_); +} + +gnutls_certificate_credentials_t TLSClientContext::get_native_handle() const { + return cred_; +} + +int TLSClientContext::init(const char *private_key_file, + const char *cert_file) { + + if (auto rv = gnutls_certificate_allocate_credentials(&cred_); rv != 0) { + std::cerr << "gnutls_certificate_allocate_credentials failed: " + << gnutls_strerror(rv) << std::endl; + return -1; + } + + if (auto rv = gnutls_certificate_set_x509_system_trust(cred_); rv < 0) { + std::cerr << "gnutls_certificate_set_x509_system_trust failed: " + << gnutls_strerror(rv) << std::endl; + return -1; + } + + if (private_key_file != nullptr && cert_file != nullptr) { + if (auto rv = gnutls_certificate_set_x509_key_file( + cred_, cert_file, private_key_file, GNUTLS_X509_FMT_PEM); + rv != 0) { + std::cerr << "gnutls_certificate_set_x509_key_file failed: " + << gnutls_strerror(rv) << std::endl; + return -1; + } + } + + return 0; +} diff --git a/examples/tls_client_context_gnutls.h b/examples/tls_client_context_gnutls.h new file mode 100644 index 0000000..f637a15 --- /dev/null +++ b/examples/tls_client_context_gnutls.h @@ -0,0 +1,50 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_CONTEXT_GNUTLS_H +#define TLS_CLIENT_CONTEXT_GNUTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <gnutls/gnutls.h> + +class TLSClientContext { +public: + TLSClientContext(); + ~TLSClientContext(); + + int init(const char *private_key_file, const char *cert_file); + + gnutls_certificate_credentials_t get_native_handle() const; + + // Keylog is enabled per session. + void enable_keylog() {} + +private: + gnutls_certificate_credentials_t cred_; +}; + +#endif // TLS_CLIENT_CONTEXT_GNUTLS_H diff --git a/examples/tls_client_context_openssl.cc b/examples/tls_client_context_openssl.cc new file mode 100644 index 0000000..06c8af1 --- /dev/null +++ b/examples/tls_client_context_openssl.cc @@ -0,0 +1,137 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_context_openssl.h" + +#include <iostream> +#include <fstream> +#include <limits> + +#include <ngtcp2/ngtcp2_crypto_openssl.h> + +#include <openssl/err.h> + +#include "client_base.h" +#include "template.h" + +extern Config config; + +TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {} + +TLSClientContext::~TLSClientContext() { + if (ssl_ctx_) { + SSL_CTX_free(ssl_ctx_); + } +} + +SSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; } + +namespace { +int new_session_cb(SSL *ssl, SSL_SESSION *session) { + if (SSL_SESSION_get_max_early_data(session) != + std::numeric_limits<uint32_t>::max()) { + std::cerr << "max_early_data_size is not 0xffffffff" << std::endl; + } + auto f = BIO_new_file(config.session_file, "w"); + if (f == nullptr) { + std::cerr << "Could not write TLS session in " << config.session_file + << std::endl; + return 0; + } + + if (!PEM_write_bio_SSL_SESSION(f, session)) { + std::cerr << "Unable to write TLS session to file" << std::endl; + } + + BIO_free(f); + + return 0; +} +} // namespace + +int TLSClientContext::init(const char *private_key_file, + const char *cert_file) { + ssl_ctx_ = SSL_CTX_new(TLS_client_method()); + if (!ssl_ctx_) { + std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + if (ngtcp2_crypto_openssl_configure_client_context(ssl_ctx_) != 0) { + std::cerr << "ngtcp2_crypto_openssl_configure_client_context failed" + << std::endl; + return -1; + } + + SSL_CTX_set_default_verify_paths(ssl_ctx_); + + if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) { + std::cerr << "SSL_CTX_set_ciphersuites: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) { + std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; + return -1; + } + + if (private_key_file && cert_file) { + if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, + SSL_FILETYPE_PEM) != 1) { + std::cerr << "SSL_CTX_use_PrivateKey_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) { + std::cerr << "SSL_CTX_use_certificate_chain_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + } + + if (config.session_file) { + SSL_CTX_set_session_cache_mode(ssl_ctx_, SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb); + } + + return 0; +} + +extern std::ofstream keylog_file; + +namespace { +void keylog_callback(const SSL *ssl, const char *line) { + keylog_file.write(line, strlen(line)); + keylog_file.put('\n'); + keylog_file.flush(); +} +} // namespace + +void TLSClientContext::enable_keylog() { + SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); +} diff --git a/examples/tls_client_context_openssl.h b/examples/tls_client_context_openssl.h new file mode 100644 index 0000000..a6d0114 --- /dev/null +++ b/examples/tls_client_context_openssl.h @@ -0,0 +1,49 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_CONTEXT_OPENSSL_H +#define TLS_CLIENT_CONTEXT_OPENSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <openssl/ssl.h> + +class TLSClientContext { +public: + TLSClientContext(); + ~TLSClientContext(); + + int init(const char *private_key_file, const char *cert_file); + + SSL_CTX *get_native_handle() const; + + void enable_keylog(); + +private: + SSL_CTX *ssl_ctx_; +}; + +#endif // TLS_CLIENT_CONTEXT_OPENSSL_H diff --git a/examples/tls_client_context_picotls.cc b/examples/tls_client_context_picotls.cc new file mode 100644 index 0000000..363f6c7 --- /dev/null +++ b/examples/tls_client_context_picotls.cc @@ -0,0 +1,155 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_context_picotls.h" + +#include <iostream> + +#include <ngtcp2/ngtcp2_crypto_picotls.h> + +#include <openssl/bio.h> +#include <openssl/pem.h> + +#include "client_base.h" +#include "template.h" + +extern Config config; + +namespace { +int save_ticket_cb(ptls_save_ticket_t *self, ptls_t *ptls, ptls_iovec_t input) { + auto f = BIO_new_file(config.session_file, "w"); + if (f == nullptr) { + std::cerr << "Could not write TLS session in " << config.session_file + << std::endl; + return 0; + } + + if (!PEM_write_bio(f, "PICOTLS SESSION PARAMETERS", "", input.base, + input.len)) { + std::cerr << "Unable to write TLS session to file" << std::endl; + } + + BIO_free(f); + + return 0; +} + +ptls_save_ticket_t save_ticket = {save_ticket_cb}; +} // namespace + +namespace { +ptls_key_exchange_algorithm_t *key_exchanges[] = { + &ptls_openssl_x25519, + &ptls_openssl_secp256r1, + &ptls_openssl_secp384r1, + &ptls_openssl_secp521r1, + nullptr, +}; +} // namespace + +namespace { +ptls_cipher_suite_t *cipher_suites[] = { + &ptls_openssl_aes128gcmsha256, + &ptls_openssl_aes256gcmsha384, + &ptls_openssl_chacha20poly1305sha256, + nullptr, +}; +} // namespace + +TLSClientContext::TLSClientContext() + : ctx_{ + .random_bytes = ptls_openssl_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = key_exchanges, + .cipher_suites = cipher_suites, + .require_dhe_on_psk = 1, + } {} + +TLSClientContext::~TLSClientContext() { + if (sign_cert_.key) { + ptls_openssl_dispose_sign_certificate(&sign_cert_); + } + + for (size_t i = 0; i < ctx_.certificates.count; ++i) { + free(ctx_.certificates.list[i].base); + } + free(ctx_.certificates.list); +} + +ptls_context_t *TLSClientContext::get_native_handle() { return &ctx_; } + +int TLSClientContext::init(const char *private_key_file, + const char *cert_file) { + if (ngtcp2_crypto_picotls_configure_client_context(&ctx_) != 0) { + std::cerr << "ngtcp2_crypto_picotls_configure_client_context failed" + << std::endl; + return -1; + } + + if (config.session_file) { + ctx_.save_ticket = &save_ticket; + } + + if (private_key_file && cert_file) { + if (ptls_load_certificates(&ctx_, cert_file) != 0) { + std::cerr << "ptls_load_certificates failed" << std::endl; + return -1; + } + + if (load_private_key(private_key_file) != 0) { + return -1; + } + } + + return 0; +} + +int TLSClientContext::load_private_key(const char *private_key_file) { + auto fp = fopen(private_key_file, "rb"); + if (fp == nullptr) { + std::cerr << "Could not open private key file " << private_key_file << ": " + << strerror(errno) << std::endl; + return -1; + } + + auto fp_d = defer(fclose, fp); + + auto pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); + if (pkey == nullptr) { + std::cerr << "Could not read private key file " << private_key_file + << std::endl; + return -1; + } + + auto pkey_d = defer(EVP_PKEY_free, pkey); + + if (ptls_openssl_init_sign_certificate(&sign_cert_, pkey) != 0) { + std::cerr << "ptls_openssl_init_sign_certificate failed" << std::endl; + return -1; + } + + ctx_.sign_certificate = &sign_cert_.super; + + return 0; +} diff --git a/examples/tls_client_context_picotls.h b/examples/tls_client_context_picotls.h new file mode 100644 index 0000000..aada78e --- /dev/null +++ b/examples/tls_client_context_picotls.h @@ -0,0 +1,54 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_CONTEXT_PICOTLS_H +#define TLS_CLIENT_CONTEXT_PICOTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <picotls.h> +#include <picotls/openssl.h> + +class TLSClientContext { +public: + TLSClientContext(); + ~TLSClientContext(); + + int init(const char *private_key_file, const char *cert_file); + + ptls_context_t *get_native_handle(); + + // TODO Implement keylog. + void enable_keylog() {} + +private: + int load_private_key(const char *private_key_file); + + ptls_context_t ctx_; + ptls_openssl_sign_certificate_t sign_cert_; +}; + +#endif // TLS_CLIENT_CONTEXT_PICOTLS_H diff --git a/examples/tls_client_context_wolfssl.cc b/examples/tls_client_context_wolfssl.cc new file mode 100644 index 0000000..0141df5 --- /dev/null +++ b/examples/tls_client_context_wolfssl.cc @@ -0,0 +1,177 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_context_wolfssl.h" + +#include <iostream> +#include <fstream> +#include <limits> + +#include <ngtcp2/ngtcp2_crypto_wolfssl.h> + +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> + +#include "client_base.h" +#include "template.h" + +extern Config config; + +TLSClientContext::TLSClientContext() : ssl_ctx_{nullptr} {} + +TLSClientContext::~TLSClientContext() { + if (ssl_ctx_) { + wolfSSL_CTX_free(ssl_ctx_); + } +} + +WOLFSSL_CTX *TLSClientContext::get_native_handle() const { return ssl_ctx_; } + +namespace { +int new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) { + std::cerr << "new_session_cb called" << std::endl; +#ifdef HAVE_SESSION_TICKET + if (wolfSSL_SESSION_get_max_early_data(session) != + std::numeric_limits<uint32_t>::max()) { + std::cerr << "max_early_data_size is not 0xffffffff" << std::endl; + } + + unsigned char sbuffer[16 * 1024], *data; + unsigned int sz; + sz = wolfSSL_i2d_SSL_SESSION(session, nullptr); + if (sz <= 0) { + std::cerr << "Could not export TLS session in " << config.session_file + << std::endl; + return 0; + } + if (static_cast<size_t>(sz) > sizeof(sbuffer)) { + std::cerr << "Exported TLS session too large" << std::endl; + return 0; + } + data = sbuffer; + sz = wolfSSL_i2d_SSL_SESSION(session, &data); + + auto f = wolfSSL_BIO_new_file(config.session_file, "w"); + if (f == nullptr) { + std::cerr << "Could not write TLS session in " << config.session_file + << std::endl; + return 0; + } + + auto f_d = defer(wolfSSL_BIO_free, f); + + if (!wolfSSL_PEM_write_bio(f, "WOLFSSL SESSION PARAMETERS", "", sbuffer, + sz)) { + std::cerr << "Unable to write TLS session to file" << std::endl; + return 0; + } + std::cerr << "new_session_cb: wrote " << sz << " of session data" + << std::endl; +#else + std::cerr << "TLS session tickets not enabled in wolfSSL " << std::endl; +#endif + return 0; +} +} // namespace + +int TLSClientContext::init(const char *private_key_file, + const char *cert_file) { + ssl_ctx_ = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if (!ssl_ctx_) { + std::cerr << "wolfSSL_CTX_new: " + << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + if (ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx_) != 0) { + std::cerr << "ngtcp2_crypto_wolfssl_configure_client_context failed" + << std::endl; + return -1; + } + + if (wolfSSL_CTX_set_default_verify_paths(ssl_ctx_) == + WOLFSSL_NOT_IMPLEMENTED) { + /* hmm, not verifying the server cert for now */ + wolfSSL_CTX_set_verify(ssl_ctx_, WOLFSSL_VERIFY_NONE, 0); + } + + if (wolfSSL_CTX_set_cipher_list(ssl_ctx_, config.ciphers) != + WOLFSSL_SUCCESS) { + std::cerr << "wolfSSL_CTX_set_cipher_list: " + << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + if (wolfSSL_CTX_set1_curves_list( + ssl_ctx_, const_cast<char *>(config.groups)) != WOLFSSL_SUCCESS) { + std::cerr << "wolfSSL_CTX_set1_curves_list(" << config.groups << ") failed" + << std::endl; + return -1; + } + + if (private_key_file && cert_file) { + if (wolfSSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, + SSL_FILETYPE_PEM) != WOLFSSL_SUCCESS) { + std::cerr << "wolfSSL_CTX_use_PrivateKey_file: " + << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + if (wolfSSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != + WOLFSSL_SUCCESS) { + std::cerr << "wolfSSL_CTX_use_certificate_chain_file: " + << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr) + << std::endl; + return -1; + } + } + + if (config.session_file) { + wolfSSL_CTX_UseSessionTicket(ssl_ctx_); + wolfSSL_CTX_sess_set_new_cb(ssl_ctx_, new_session_cb); + } + + return 0; +} + +extern std::ofstream keylog_file; + +#ifdef HAVE_SECRET_CALLBACK +namespace { +void keylog_callback(const WOLFSSL *ssl, const char *line) { + keylog_file.write(line, strlen(line)); + keylog_file.put('\n'); + keylog_file.flush(); +} +} // namespace +#endif + +void TLSClientContext::enable_keylog() { +#ifdef HAVE_SECRET_CALLBACK + wolfSSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); +#endif +} diff --git a/examples/tls_client_context_wolfssl.h b/examples/tls_client_context_wolfssl.h new file mode 100644 index 0000000..f2ed14e --- /dev/null +++ b/examples/tls_client_context_wolfssl.h @@ -0,0 +1,51 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_CONTEXT_WOLFSSL_H +#define TLS_CLIENT_CONTEXT_WOLFSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> +#include <wolfssl/quic.h> + +class TLSClientContext { +public: + TLSClientContext(); + ~TLSClientContext(); + + int init(const char *private_key_file, const char *cert_file); + + WOLFSSL_CTX *get_native_handle() const; + + void enable_keylog(); + +private: + WOLFSSL_CTX *ssl_ctx_; +}; + +#endif // TLS_CLIENT_CONTEXT_WOLFSSL_H diff --git a/examples/tls_client_session.h b/examples/tls_client_session.h new file mode 100644 index 0000000..e4fd0f2 --- /dev/null +++ b/examples/tls_client_session.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_SESSION_H +#define TLS_CLIENT_SESSION_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#if defined(ENABLE_EXAMPLE_OPENSSL) && defined(WITH_EXAMPLE_OPENSSL) +# include "tls_client_session_openssl.h" +#endif // ENABLE_EXAMPLE_OPENSSL && WITH_EXAMPLE_OPENSSL + +#if defined(ENABLE_EXAMPLE_GNUTLS) && defined(WITH_EXAMPLE_GNUTLS) +# include "tls_client_session_gnutls.h" +#endif // ENABLE_EXAMPLE_GNUTLS && WITH_EXAMPLE_GNUTLS + +#if defined(ENABLE_EXAMPLE_BORINGSSL) && defined(WITH_EXAMPLE_BORINGSSL) +# include "tls_client_session_boringssl.h" +#endif // ENABLE_EXAMPLE_BORINGSSL && WITH_EXAMPLE_BORINGSSL + +#if defined(ENABLE_EXAMPLE_PICOTLS) && defined(WITH_EXAMPLE_PICOTLS) +# include "tls_client_session_picotls.h" +#endif // ENABLE_EXAMPLE_PICOTLS && WITH_EXAMPLE_PICOTLS + +#if defined(ENABLE_EXAMPLE_WOLFSSL) && defined(WITH_EXAMPLE_WOLFSSL) +# include "tls_client_session_wolfssl.h" +#endif // ENABLE_EXAMPLE_WOLFSSL && WITH_EXAMPLE_WOLFSSL + +#endif // TLS_CLIENT_SESSION_H diff --git a/examples/tls_client_session_boringssl.cc b/examples/tls_client_session_boringssl.cc new file mode 100644 index 0000000..95b9834 --- /dev/null +++ b/examples/tls_client_session_boringssl.cc @@ -0,0 +1,110 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_session_boringssl.h" + +#include <cassert> +#include <iostream> + +#include "tls_client_context_boringssl.h" +#include "client_base.h" +#include "template.h" +#include "util.h" + +TLSClientSession::TLSClientSession() {} + +TLSClientSession::~TLSClientSession() {} + +extern Config config; + +int TLSClientSession::init(bool &early_data_enabled, + const TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, + uint32_t quic_version, AppProtocol app_proto) { + early_data_enabled = false; + + auto ssl_ctx = tls_ctx.get_native_handle(); + + ssl_ = SSL_new(ssl_ctx); + if (!ssl_) { + std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + SSL_set_app_data(ssl_, client->conn_ref()); + SSL_set_connect_state(ssl_); + + SSL_set_quic_use_legacy_codepoint(ssl_, + (quic_version & 0xff000000) == 0xff000000); + + switch (app_proto) { + case AppProtocol::H3: + SSL_set_alpn_protos(ssl_, H3_ALPN, str_size(H3_ALPN)); + break; + case AppProtocol::HQ: + SSL_set_alpn_protos(ssl_, HQ_ALPN, str_size(HQ_ALPN)); + break; + } + + if (!config.sni.empty()) { + SSL_set_tlsext_host_name(ssl_, config.sni.data()); + } else if (util::numeric_host(remote_addr)) { + // If remote host is numeric address, just send "localhost" as SNI + // for now. + SSL_set_tlsext_host_name(ssl_, "localhost"); + } else { + SSL_set_tlsext_host_name(ssl_, remote_addr); + } + + if (config.session_file) { + auto f = BIO_new_file(config.session_file, "r"); + if (f == nullptr) { + std::cerr << "Could not read TLS session file " << config.session_file + << std::endl; + } else { + auto session = PEM_read_bio_SSL_SESSION(f, nullptr, 0, nullptr); + BIO_free(f); + if (session == nullptr) { + std::cerr << "Could not read TLS session file " << config.session_file + << std::endl; + } else { + if (!SSL_set_session(ssl_, session)) { + std::cerr << "Could not set session" << std::endl; + } else if (!config.disable_early_data && + SSL_SESSION_early_data_capable(session)) { + early_data_enabled = true; + SSL_set_early_data_enabled(ssl_, 1); + } + SSL_SESSION_free(session); + } + } + } + + return 0; +} + +bool TLSClientSession::get_early_data_accepted() const { + return SSL_early_data_accepted(ssl_); +} diff --git a/examples/tls_client_session_boringssl.h b/examples/tls_client_session_boringssl.h new file mode 100644 index 0000000..27ce9ab --- /dev/null +++ b/examples/tls_client_session_boringssl.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_SESSION_BORINGSSL_H +#define TLS_CLIENT_SESSION_BORINGSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_openssl.h" +#include "shared.h" + +using namespace ngtcp2; + +class TLSClientContext; +class ClientBase; + +class TLSClientSession : public TLSSessionBase { +public: + TLSClientSession(); + ~TLSClientSession(); + + int init(bool &early_data_enabled, const TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, uint32_t quic_version, + AppProtocol app_proto); + + bool get_early_data_accepted() const; +}; + +#endif // TLS_CLIENT_SESSION_BORINGSSL_H diff --git a/examples/tls_client_session_gnutls.cc b/examples/tls_client_session_gnutls.cc new file mode 100644 index 0000000..c77394f --- /dev/null +++ b/examples/tls_client_session_gnutls.cc @@ -0,0 +1,190 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_session_gnutls.h" + +#include <iostream> +#include <fstream> +#include <array> + +#include <ngtcp2/ngtcp2_crypto_gnutls.h> + +#include <gnutls/crypto.h> + +#include "tls_client_context_gnutls.h" +#include "client_base.h" +#include "template.h" +#include "util.h" + +// Based on https://github.com/ueno/ngtcp2-gnutls-examples + +extern Config config; + +TLSClientSession::TLSClientSession() {} + +TLSClientSession::~TLSClientSession() {} + +namespace { +int hook_func(gnutls_session_t session, unsigned int htype, unsigned when, + unsigned int incoming, const gnutls_datum_t *msg) { + if (config.session_file && htype == GNUTLS_HANDSHAKE_NEW_SESSION_TICKET) { + gnutls_datum_t data; + if (auto rv = gnutls_session_get_data2(session, &data); rv != 0) { + std::cerr << "gnutls_session_get_data2 failed: " << gnutls_strerror(rv) + << std::endl; + return rv; + } + auto f = std::ofstream(config.session_file); + if (!f) { + return -1; + } + + gnutls_datum_t d; + if (auto rv = + gnutls_pem_base64_encode2("GNUTLS SESSION PARAMETERS", &data, &d); + rv < 0) { + std::cerr << "Could not encode session in " << config.session_file + << std::endl; + return -1; + } + + f.write(reinterpret_cast<const char *>(d.data), d.size); + if (!f) { + std::cerr << "Unable to write TLS session to file" << std::endl; + } + gnutls_free(d.data); + gnutls_free(data.data); + } + + return 0; +} +} // namespace + +int TLSClientSession::init(bool &early_data_enabled, + const TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, + uint32_t quic_version, AppProtocol app_proto) { + early_data_enabled = false; + + if (auto rv = + gnutls_init(&session_, GNUTLS_CLIENT | GNUTLS_ENABLE_EARLY_DATA | + GNUTLS_NO_END_OF_EARLY_DATA); + rv != 0) { + std::cerr << "gnutls_init failed: " << gnutls_strerror(rv) << std::endl; + return -1; + } + + std::string priority = "%DISABLE_TLS13_COMPAT_MODE:"; + priority += config.ciphers; + priority += ':'; + priority += config.groups; + + if (auto rv = gnutls_priority_set_direct(session_, priority.c_str(), nullptr); + rv != 0) { + std::cerr << "gnutls_priority_set_direct failed: " << gnutls_strerror(rv) + << std::endl; + return -1; + } + + gnutls_handshake_set_hook_function(session_, GNUTLS_HANDSHAKE_ANY, + GNUTLS_HOOK_POST, hook_func); + + if (ngtcp2_crypto_gnutls_configure_client_session(session_) != 0) { + std::cerr << "ngtcp2_crypto_gnutls_configure_client_session failed" + << std::endl; + return -1; + } + + if (config.session_file) { + auto f = std::ifstream(config.session_file); + if (f) { + f.seekg(0, std::ios::end); + auto pos = f.tellg(); + std::vector<char> content(pos); + f.seekg(0, std::ios::beg); + f.read(content.data(), pos); + + gnutls_datum_t s{ + .data = reinterpret_cast<unsigned char *>(content.data()), + .size = static_cast<unsigned int>(content.size()), + }; + + gnutls_datum_t d; + if (auto rv = + gnutls_pem_base64_decode2("GNUTLS SESSION PARAMETERS", &s, &d); + rv < 0) { + std::cerr << "Could not read session in " << config.session_file + << std::endl; + return -1; + } + + auto d_d = defer(gnutls_free, d.data); + + if (auto rv = gnutls_session_set_data(session_, d.data, d.size); + rv != 0) { + std::cerr << "gnutls_session_set_data failed: " << gnutls_strerror(rv) + << std::endl; + return -1; + } + + if (!config.disable_early_data) { + early_data_enabled = true; + } + } + } + + gnutls_session_set_ptr(session_, client->conn_ref()); + + if (auto rv = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, + tls_ctx.get_native_handle()); + rv != 0) { + std::cerr << "gnutls_credentials_set failed: " << gnutls_strerror(rv) + << std::endl; + return -1; + } + + // strip the first byte from H3_ALPN_V1 + gnutls_datum_t alpn{ + .data = const_cast<uint8_t *>(&H3_ALPN_V1[1]), + .size = H3_ALPN_V1[0], + }; + + gnutls_alpn_set_protocols(session_, &alpn, 1, GNUTLS_ALPN_MANDATORY); + + if (util::numeric_host(remote_addr)) { + // If remote host is numeric address, just send "localhost" as SNI + // for now. + gnutls_server_name_set(session_, GNUTLS_NAME_DNS, "localhost", + strlen("localhost")); + } else { + gnutls_server_name_set(session_, GNUTLS_NAME_DNS, remote_addr, + strlen(remote_addr)); + } + + return 0; +} + +bool TLSClientSession::get_early_data_accepted() const { + return gnutls_session_get_flags(session_) & GNUTLS_SFLAGS_EARLY_DATA; +} diff --git a/examples/tls_client_session_gnutls.h b/examples/tls_client_session_gnutls.h new file mode 100644 index 0000000..a76db49 --- /dev/null +++ b/examples/tls_client_session_gnutls.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_SESSION_GNUTLS_H +#define TLS_CLIENT_SESSION_GNUTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_gnutls.h" +#include "shared.h" + +using namespace ngtcp2; + +class TLSClientContext; +class ClientBase; + +class TLSClientSession : public TLSSessionBase { +public: + TLSClientSession(); + ~TLSClientSession(); + + int init(bool &early_data_enabled, const TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, uint32_t quic_version, + AppProtocol app_proto); + + bool get_early_data_accepted() const; +}; + +#endif // TLS_CLIENT_SESSION_GNUTLS_H diff --git a/examples/tls_client_session_openssl.cc b/examples/tls_client_session_openssl.cc new file mode 100644 index 0000000..dd6bb5d --- /dev/null +++ b/examples/tls_client_session_openssl.cc @@ -0,0 +1,113 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_session_openssl.h" + +#include <cassert> +#include <iostream> + +#include <openssl/err.h> + +#include "tls_client_context_openssl.h" +#include "client_base.h" +#include "template.h" +#include "util.h" + +TLSClientSession::TLSClientSession() {} + +TLSClientSession::~TLSClientSession() {} + +extern Config config; + +int TLSClientSession::init(bool &early_data_enabled, + const TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, + uint32_t quic_version, AppProtocol app_proto) { + early_data_enabled = false; + + auto ssl_ctx = tls_ctx.get_native_handle(); + + ssl_ = SSL_new(ssl_ctx); + if (!ssl_) { + std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + SSL_set_app_data(ssl_, client->conn_ref()); + SSL_set_connect_state(ssl_); + + SSL_set_quic_use_legacy_codepoint(ssl_, + (quic_version & 0xff000000) == 0xff000000); + + switch (app_proto) { + case AppProtocol::H3: + SSL_set_alpn_protos(ssl_, H3_ALPN, str_size(H3_ALPN)); + break; + case AppProtocol::HQ: + SSL_set_alpn_protos(ssl_, HQ_ALPN, str_size(HQ_ALPN)); + break; + } + + if (!config.sni.empty()) { + SSL_set_tlsext_host_name(ssl_, config.sni.data()); + } else if (util::numeric_host(remote_addr)) { + // If remote host is numeric address, just send "localhost" as SNI + // for now. + SSL_set_tlsext_host_name(ssl_, "localhost"); + } else { + SSL_set_tlsext_host_name(ssl_, remote_addr); + } + + if (config.session_file) { + auto f = BIO_new_file(config.session_file, "r"); + if (f == nullptr) { + std::cerr << "Could not read TLS session file " << config.session_file + << std::endl; + } else { + auto session = PEM_read_bio_SSL_SESSION(f, nullptr, 0, nullptr); + BIO_free(f); + if (session == nullptr) { + std::cerr << "Could not read TLS session file " << config.session_file + << std::endl; + } else { + if (!SSL_set_session(ssl_, session)) { + std::cerr << "Could not set session" << std::endl; + } else if (!config.disable_early_data && + SSL_SESSION_get_max_early_data(session)) { + early_data_enabled = true; + SSL_set_quic_early_data_enabled(ssl_, 1); + } + SSL_SESSION_free(session); + } + } + } + + return 0; +} + +bool TLSClientSession::get_early_data_accepted() const { + // SSL_get_early_data_status works after handshake completes. + return SSL_get_early_data_status(ssl_) == SSL_EARLY_DATA_ACCEPTED; +} diff --git a/examples/tls_client_session_openssl.h b/examples/tls_client_session_openssl.h new file mode 100644 index 0000000..06ecb41 --- /dev/null +++ b/examples/tls_client_session_openssl.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_SESSION_OPENSSL_H +#define TLS_CLIENT_SESSION_OPENSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_openssl.h" +#include "shared.h" + +using namespace ngtcp2; + +class TLSClientContext; +class ClientBase; + +class TLSClientSession : public TLSSessionBase { +public: + TLSClientSession(); + ~TLSClientSession(); + + int init(bool &early_data_enabled, const TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, uint32_t quic_version, + AppProtocol app_proto); + + bool get_early_data_accepted() const; +}; + +#endif // TLS_CLIENT_SESSION_OPENSSL_H diff --git a/examples/tls_client_session_picotls.cc b/examples/tls_client_session_picotls.cc new file mode 100644 index 0000000..8f5bdfc --- /dev/null +++ b/examples/tls_client_session_picotls.cc @@ -0,0 +1,147 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_session_picotls.h" + +#include <iostream> +#include <memory> + +#include <ngtcp2/ngtcp2_crypto_picotls.h> + +#include <openssl/bio.h> +#include <openssl/pem.h> + +#include <picotls.h> + +#include "tls_client_context_picotls.h" +#include "client_base.h" +#include "template.h" +#include "util.h" + +using namespace std::literals; + +extern Config config; + +TLSClientSession::TLSClientSession() {} + +TLSClientSession::~TLSClientSession() { + auto &hsprops = cptls_.handshake_properties; + + delete[] hsprops.client.session_ticket.base; +} + +namespace { +auto negotiated_protocols = std::array<ptls_iovec_t, 1>{{ + { + .base = const_cast<uint8_t *>(&H3_ALPN_V1[1]), + .len = H3_ALPN_V1[0], + }, +}}; +} // namespace + +int TLSClientSession::init(bool &early_data_enabled, TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, + uint32_t quic_version, AppProtocol app_proto) { + cptls_.ptls = ptls_client_new(tls_ctx.get_native_handle()); + if (!cptls_.ptls) { + std::cerr << "ptls_client_new failed" << std::endl; + return -1; + } + + *ptls_get_data_ptr(cptls_.ptls) = client->conn_ref(); + + auto conn = client->conn(); + auto &hsprops = cptls_.handshake_properties; + + hsprops.additional_extensions = new ptls_raw_extension_t[2]{ + { + .type = UINT16_MAX, + }, + { + .type = UINT16_MAX, + }, + }; + + if (ngtcp2_crypto_picotls_configure_client_session(&cptls_, conn) != 0) { + std::cerr << "ngtcp2_crypto_picotls_configure_client_session failed" + << std::endl; + return -1; + } + + hsprops.client.negotiated_protocols.list = negotiated_protocols.data(); + hsprops.client.negotiated_protocols.count = negotiated_protocols.size(); + + if (util::numeric_host(remote_addr)) { + // If remote host is numeric address, just send "localhost" as SNI + // for now. + ptls_set_server_name(cptls_.ptls, "localhost", strlen("localhost")); + } else { + ptls_set_server_name(cptls_.ptls, remote_addr, strlen(remote_addr)); + } + + if (config.session_file) { + auto f = BIO_new_file(config.session_file, "r"); + if (f == nullptr) { + std::cerr << "Could not read TLS session file " << config.session_file + << std::endl; + } else { + auto f_d = defer(BIO_free, f); + + char *name, *header; + unsigned char *data; + long datalen; + + if (PEM_read_bio(f, &name, &header, &data, &datalen) != 1) { + std::cerr << "Could not read TLS session file " << config.session_file + << std::endl; + } else { + if ("PICOTLS SESSION PARAMETERS"sv != name) { + std::cerr << "TLS session file contains unexpected name: " << name + << std::endl; + } else { + hsprops.client.session_ticket.base = new uint8_t[datalen]; + hsprops.client.session_ticket.len = datalen; + memcpy(hsprops.client.session_ticket.base, data, datalen); + + if (!config.disable_early_data) { + // No easy way to check max_early_data from ticket. We + // need to run ptls_handle_message. + early_data_enabled = true; + } + } + + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(data); + } + } + } + + return 0; +} + +bool TLSClientSession::get_early_data_accepted() const { + return cptls_.handshake_properties.client.early_data_acceptance == + PTLS_EARLY_DATA_ACCEPTED; +} diff --git a/examples/tls_client_session_picotls.h b/examples/tls_client_session_picotls.h new file mode 100644 index 0000000..75a376e --- /dev/null +++ b/examples/tls_client_session_picotls.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_SESSION_PICOTLS_H +#define TLS_CLIENT_SESSION_PICOTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_picotls.h" +#include "shared.h" + +using namespace ngtcp2; + +class TLSClientContext; +class ClientBase; + +class TLSClientSession : public TLSSessionBase { +public: + TLSClientSession(); + ~TLSClientSession(); + + int init(bool &early_data_enabled, TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, uint32_t quic_version, + AppProtocol app_proto); + + bool get_early_data_accepted() const; +}; + +#endif // TLS_CLIENT_SESSION_PICOTLS_H diff --git a/examples/tls_client_session_wolfssl.cc b/examples/tls_client_session_wolfssl.cc new file mode 100644 index 0000000..87fb809 --- /dev/null +++ b/examples/tls_client_session_wolfssl.cc @@ -0,0 +1,160 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_client_session_wolfssl.h" + +#include <cassert> +#include <iostream> + +#include "tls_client_context_wolfssl.h" +#include "client_base.h" +#include "template.h" +#include "util.h" + +using namespace std::literals; + +TLSClientSession::TLSClientSession() {} + +TLSClientSession::~TLSClientSession() {} + +extern Config config; + +namespace { +int wolfssl_session_ticket_cb(WOLFSSL *ssl, const unsigned char *ticket, + int ticketSz, void *cb_ctx) { + std::cerr << "session ticket calback invoked" << std::endl; + return 0; +} +} // namespace + +int TLSClientSession::init(bool &early_data_enabled, + const TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, + uint32_t quic_version, AppProtocol app_proto) { + early_data_enabled = false; + + auto ssl_ctx = tls_ctx.get_native_handle(); + + ssl_ = wolfSSL_new(ssl_ctx); + if (!ssl_) { + std::cerr << "wolfSSL_new: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + wolfSSL_set_app_data(ssl_, client->conn_ref()); + wolfSSL_set_connect_state(ssl_); + + wolfSSL_set_quic_use_legacy_codepoint(ssl_, (quic_version & 0xff000000) == + 0xff000000); + + switch (app_proto) { + case AppProtocol::H3: + wolfSSL_set_alpn_protos(ssl_, H3_ALPN, str_size(H3_ALPN)); + break; + case AppProtocol::HQ: + wolfSSL_set_alpn_protos(ssl_, HQ_ALPN, str_size(HQ_ALPN)); + break; + } + + if (!config.sni.empty()) { + wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, config.sni.data(), + config.sni.length()); + } else if (util::numeric_host(remote_addr)) { + // If remote host is numeric address, just send "localhost" as SNI + // for now. + wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, "localhost", + sizeof("localhost") - 1); + } else { + wolfSSL_UseSNI(ssl_, WOLFSSL_SNI_HOST_NAME, remote_addr, + strlen(remote_addr)); + } + + if (config.session_file) { +#ifdef HAVE_SESSION_TICKET + auto f = wolfSSL_BIO_new_file(config.session_file, "r"); + if (f == nullptr) { + std::cerr << "Could not open TLS session file " << config.session_file + << std::endl; + } else { + char *name, *header; + unsigned char *data; + const unsigned char *pdata; + long datalen; + unsigned int ret; + WOLFSSL_SESSION *session; + + if (wolfSSL_PEM_read_bio(f, &name, &header, &data, &datalen) != 1) { + std::cerr << "Could not read TLS session file " << config.session_file + << std::endl; + } else { + if ("WOLFSSL SESSION PARAMETERS"sv != name) { + std::cerr << "TLS session file contains unexpected name: " << name + << std::endl; + } else { + pdata = data; + session = wolfSSL_d2i_SSL_SESSION(nullptr, &pdata, datalen); + if (session == nullptr) { + std::cerr << "Could not parse TLS session from file " + << config.session_file << std::endl; + } else { + ret = wolfSSL_set_session(ssl_, session); + if (ret != WOLFSSL_SUCCESS) { + std::cerr << "Could not install TLS session from file " + << config.session_file << std::endl; + } else { + if (!config.disable_early_data && + wolfSSL_SESSION_get_max_early_data(session)) { + early_data_enabled = true; + wolfSSL_set_quic_early_data_enabled(ssl_, 1); + } + } + wolfSSL_SESSION_free(session); + } + } + + wolfSSL_OPENSSL_free(name); + wolfSSL_OPENSSL_free(header); + wolfSSL_OPENSSL_free(data); + } + wolfSSL_BIO_free(f); + } + wolfSSL_UseSessionTicket(ssl_); + wolfSSL_set_SessionTicket_cb(ssl_, wolfssl_session_ticket_cb, nullptr); +#else + std::cerr << "TLS session im-/export not enabled in wolfSSL" << std::endl; +#endif + } + + return 0; +} + +bool TLSClientSession::get_early_data_accepted() const { + // wolfSSL_get_early_data_status works after handshake completes. +#ifdef WOLFSSL_EARLY_DATA + return wolfSSL_get_early_data_status(ssl_) == SSL_EARLY_DATA_ACCEPTED; +#else + return 0; +#endif +} diff --git a/examples/tls_client_session_wolfssl.h b/examples/tls_client_session_wolfssl.h new file mode 100644 index 0000000..1686e14 --- /dev/null +++ b/examples/tls_client_session_wolfssl.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_CLIENT_SESSION_WOLFSSL_H +#define TLS_CLIENT_SESSION_WOLFSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_wolfssl.h" +#include "shared.h" + +using namespace ngtcp2; + +class TLSClientContext; +class ClientBase; + +class TLSClientSession : public TLSSessionBase { +public: + TLSClientSession(); + ~TLSClientSession(); + + int init(bool &early_data_enabled, const TLSClientContext &tls_ctx, + const char *remote_addr, ClientBase *client, uint32_t quic_version, + AppProtocol app_proto); + + bool get_early_data_accepted() const; +}; + +#endif // TLS_CLIENT_SESSION_WOLFSSL_H diff --git a/examples/tls_server_context.h b/examples/tls_server_context.h new file mode 100644 index 0000000..1f4c574 --- /dev/null +++ b/examples/tls_server_context.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_CONTEXT_H +#define TLS_SERVER_CONTEXT_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#if defined(ENABLE_EXAMPLE_OPENSSL) && defined(WITH_EXAMPLE_OPENSSL) +# include "tls_server_context_openssl.h" +#endif // ENABLE_EXAMPLE_OPENSSL && WITH_EXAMPLE_OPENSSL + +#if defined(ENABLE_EXAMPLE_GNUTLS) && defined(WITH_EXAMPLE_GNUTLS) +# include "tls_server_context_gnutls.h" +#endif // ENABLE_EXAMPLE_GNUTLS && WITH_EXAMPLE_GNUTLS + +#if defined(ENABLE_EXAMPLE_BORINGSSL) && defined(WITH_EXAMPLE_BORINGSSL) +# include "tls_server_context_boringssl.h" +#endif // ENABLE_EXAMPLE_BORINGSSL && WITH_EXAMPLE_BORINGSSL + +#if defined(ENABLE_EXAMPLE_PICOTLS) && defined(WITH_EXAMPLE_PICOTLS) +# include "tls_server_context_picotls.h" +#endif // ENABLE_EXAMPLE_PICOTLS && WITH_EXAMPLE_PICOTLS + +#if defined(ENABLE_EXAMPLE_WOLFSSL) && defined(WITH_EXAMPLE_WOLFSSL) +# include "tls_server_context_wolfssl.h" +#endif // ENABLE_EXAMPLE_WOLFSSL && WITH_EXAMPLE_WOLFSSL + +#endif // TLS_SERVER_CONTEXT_H diff --git a/examples/tls_server_context_boringssl.cc b/examples/tls_server_context_boringssl.cc new file mode 100644 index 0000000..9b95583 --- /dev/null +++ b/examples/tls_server_context_boringssl.cc @@ -0,0 +1,257 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_context_boringssl.h" + +#include <iostream> +#include <fstream> + +#include <ngtcp2/ngtcp2_crypto_boringssl.h> + +#include <openssl/err.h> + +#include "server_base.h" +#include "template.h" + +extern Config config; + +TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {} + +TLSServerContext::~TLSServerContext() { + if (ssl_ctx_) { + SSL_CTX_free(ssl_ctx_); + } +} + +SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; } + +namespace { +int alpn_select_proto_h3_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl)); + auto h = static_cast<HandlerBase *>(conn_ref->user_data); + const uint8_t *alpn; + size_t alpnlen; + // This should be the negotiated version, but we have not set the + // negotiated version when this callback is called. + auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); + + switch (version) { + case QUIC_VER_DRAFT29: + alpn = H3_ALPN_DRAFT29; + alpnlen = str_size(H3_ALPN_DRAFT29); + break; + case QUIC_VER_DRAFT30: + alpn = H3_ALPN_DRAFT30; + alpnlen = str_size(H3_ALPN_DRAFT30); + break; + case QUIC_VER_DRAFT31: + alpn = H3_ALPN_DRAFT31; + alpnlen = str_size(H3_ALPN_DRAFT31); + break; + case QUIC_VER_DRAFT32: + alpn = H3_ALPN_DRAFT32; + alpnlen = str_size(H3_ALPN_DRAFT32); + break; + case NGTCP2_PROTO_VER_V1: + case NGTCP2_PROTO_VER_V2_DRAFT: + alpn = H3_ALPN_V1; + alpnlen = str_size(H3_ALPN_V1); + break; + default: + if (!config.quiet) { + std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" + << version << std::dec << std::endl; + } + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) { + if (std::equal(alpn, alpn + alpnlen, p)) { + *out = p + 1; + *outlen = *p; + return SSL_TLSEXT_ERR_OK; + } + } + + if (!config.quiet) { + std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl; + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +} // namespace + +namespace { +int alpn_select_proto_hq_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl)); + auto h = static_cast<HandlerBase *>(conn_ref->user_data); + const uint8_t *alpn; + size_t alpnlen; + // This should be the negotiated version, but we have not set the + // negotiated version when this callback is called. + auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); + + switch (version) { + case QUIC_VER_DRAFT29: + alpn = HQ_ALPN_DRAFT29; + alpnlen = str_size(HQ_ALPN_DRAFT29); + break; + case QUIC_VER_DRAFT30: + alpn = HQ_ALPN_DRAFT30; + alpnlen = str_size(HQ_ALPN_DRAFT30); + break; + case QUIC_VER_DRAFT31: + alpn = HQ_ALPN_DRAFT31; + alpnlen = str_size(HQ_ALPN_DRAFT31); + break; + case QUIC_VER_DRAFT32: + alpn = HQ_ALPN_DRAFT32; + alpnlen = str_size(HQ_ALPN_DRAFT32); + break; + case NGTCP2_PROTO_VER_V1: + case NGTCP2_PROTO_VER_V2_DRAFT: + alpn = HQ_ALPN_V1; + alpnlen = str_size(HQ_ALPN_V1); + break; + default: + if (!config.quiet) { + std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" + << version << std::dec << std::endl; + } + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) { + if (std::equal(alpn, alpn + alpnlen, p)) { + *out = p + 1; + *outlen = *p; + return SSL_TLSEXT_ERR_OK; + } + } + + if (!config.quiet) { + std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl; + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +} // namespace + +namespace { +int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { + // We don't verify the client certificate. Just request it for the + // testing purpose. + return 1; +} +} // namespace + +int TLSServerContext::init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto) { + constexpr static unsigned char sid_ctx[] = "ngtcp2 server"; + + ssl_ctx_ = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx_) { + std::cerr << "SSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + constexpr auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_SINGLE_ECDH_USE | + SSL_OP_CIPHER_SERVER_PREFERENCE; + + SSL_CTX_set_options(ssl_ctx_, ssl_opts); + + if (SSL_CTX_set1_curves_list(ssl_ctx_, config.groups) != 1) { + std::cerr << "SSL_CTX_set1_curves_list failed" << std::endl; + return -1; + } + + SSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS); + + if (ngtcp2_crypto_boringssl_configure_server_context(ssl_ctx_) != 0) { + std::cerr << "ngtcp2_crypto_boringssl_configure_server_context failed" + << std::endl; + return -1; + } + + switch (app_proto) { + case AppProtocol::H3: + SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr); + break; + case AppProtocol::HQ: + SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr); + break; + } + + SSL_CTX_set_default_verify_paths(ssl_ctx_); + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, + SSL_FILETYPE_PEM) != 1) { + std::cerr << "SSL_CTX_use_PrivateKey_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) { + std::cerr << "SSL_CTX_use_certificate_chain_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_check_private_key(ssl_ctx_) != 1) { + std::cerr << "SSL_CTX_check_private_key: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + SSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1); + + if (config.verify_client) { + SSL_CTX_set_verify(ssl_ctx_, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_cb); + } + + return 0; +} + +extern std::ofstream keylog_file; + +namespace { +void keylog_callback(const SSL *ssl, const char *line) { + keylog_file.write(line, strlen(line)); + keylog_file.put('\n'); + keylog_file.flush(); +} +} // namespace + +void TLSServerContext::enable_keylog() { + SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); +} diff --git a/examples/tls_server_context_boringssl.h b/examples/tls_server_context_boringssl.h new file mode 100644 index 0000000..d7d3dfb --- /dev/null +++ b/examples/tls_server_context_boringssl.h @@ -0,0 +1,54 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_CONTEXT_BORINGSSL_H +#define TLS_SERVER_CONTEXT_BORINGSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <openssl/ssl.h> + +#include "shared.h" + +using namespace ngtcp2; + +class TLSServerContext { +public: + TLSServerContext(); + ~TLSServerContext(); + + int init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto); + + SSL_CTX *get_native_handle() const; + + void enable_keylog(); + +private: + SSL_CTX *ssl_ctx_; +}; + +#endif // TLS_SERVER_CONTEXT_BORINGSSL_H diff --git a/examples/tls_server_context_gnutls.cc b/examples/tls_server_context_gnutls.cc new file mode 100644 index 0000000..5b2be55 --- /dev/null +++ b/examples/tls_server_context_gnutls.cc @@ -0,0 +1,99 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_context_gnutls.h" + +#include <iostream> + +#include "server_base.h" +#include "template.h" + +// Based on https://github.com/ueno/ngtcp2-gnutls-examples + +extern Config config; + +namespace { +int anti_replay_db_add_func(void *dbf, time_t exp_time, + const gnutls_datum_t *key, + const gnutls_datum_t *data) { + return 0; +} +} // namespace + +TLSServerContext::TLSServerContext() : cred_{nullptr}, session_ticket_key_{} { + gnutls_anti_replay_init(&anti_replay_); + gnutls_anti_replay_set_add_function(anti_replay_, anti_replay_db_add_func); + gnutls_anti_replay_set_ptr(anti_replay_, nullptr); +} + +TLSServerContext::~TLSServerContext() { + gnutls_anti_replay_deinit(anti_replay_); + gnutls_free(session_ticket_key_.data); + gnutls_certificate_free_credentials(cred_); +} + +gnutls_certificate_credentials_t +TLSServerContext::get_certificate_credentials() const { + return cred_; +} + +const gnutls_datum_t *TLSServerContext::get_session_ticket_key() const { + return &session_ticket_key_; +} + +gnutls_anti_replay_t TLSServerContext::get_anti_replay() const { + return anti_replay_; +} + +int TLSServerContext::init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto) { + if (auto rv = gnutls_certificate_allocate_credentials(&cred_); rv != 0) { + std::cerr << "gnutls_certificate_allocate_credentials failed: " + << gnutls_strerror(rv) << std::endl; + return -1; + } + + if (auto rv = gnutls_certificate_set_x509_system_trust(cred_); rv < 0) { + std::cerr << "gnutls_certificate_set_x509_system_trust failed: " + << gnutls_strerror(rv) << std::endl; + return -1; + } + + if (auto rv = gnutls_certificate_set_x509_key_file( + cred_, cert_file, private_key_file, GNUTLS_X509_FMT_PEM); + rv != 0) { + std::cerr << "gnutls_certificate_set_x509_key_file failed: " + << gnutls_strerror(rv) << std::endl; + return -1; + } + + if (auto rv = gnutls_session_ticket_key_generate(&session_ticket_key_); + rv != 0) { + std::cerr << "gnutls_session_ticket_key_generate failed: " + << gnutls_strerror(rv) << std::endl; + return -1; + } + + return 0; +} diff --git a/examples/tls_server_context_gnutls.h b/examples/tls_server_context_gnutls.h new file mode 100644 index 0000000..21ed109 --- /dev/null +++ b/examples/tls_server_context_gnutls.h @@ -0,0 +1,59 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_CONTEXT_GNUTLS_H +#define TLS_SERVER_CONTEXT_GNUTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <gnutls/gnutls.h> + +#include "shared.h" + +using namespace ngtcp2; + +class TLSServerContext { +public: + TLSServerContext(); + ~TLSServerContext(); + + int init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto); + + gnutls_certificate_credentials_t get_certificate_credentials() const; + const gnutls_datum_t *get_session_ticket_key() const; + gnutls_anti_replay_t get_anti_replay() const; + + // Keylog is enabled per session. + void enable_keylog() {} + +private: + gnutls_certificate_credentials_t cred_; + gnutls_datum_t session_ticket_key_; + gnutls_anti_replay_t anti_replay_; +}; + +#endif // TLS_SERVER_CONTEXT_GNUTLS_H diff --git a/examples/tls_server_context_openssl.cc b/examples/tls_server_context_openssl.cc new file mode 100644 index 0000000..bdc35a3 --- /dev/null +++ b/examples/tls_server_context_openssl.cc @@ -0,0 +1,338 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_context_openssl.h" + +#include <cstring> +#include <iostream> +#include <fstream> +#include <limits> + +#include <ngtcp2/ngtcp2_crypto_openssl.h> + +#include <openssl/err.h> + +#include "server_base.h" +#include "template.h" + +extern Config config; + +TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {} + +TLSServerContext::~TLSServerContext() { + if (ssl_ctx_) { + SSL_CTX_free(ssl_ctx_); + } +} + +SSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; } + +namespace { +int alpn_select_proto_h3_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl)); + auto h = static_cast<HandlerBase *>(conn_ref->user_data); + const uint8_t *alpn; + size_t alpnlen; + // This should be the negotiated version, but we have not set the + // negotiated version when this callback is called. + auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); + + switch (version) { + case QUIC_VER_DRAFT29: + alpn = H3_ALPN_DRAFT29; + alpnlen = str_size(H3_ALPN_DRAFT29); + break; + case QUIC_VER_DRAFT30: + alpn = H3_ALPN_DRAFT30; + alpnlen = str_size(H3_ALPN_DRAFT30); + break; + case QUIC_VER_DRAFT31: + alpn = H3_ALPN_DRAFT31; + alpnlen = str_size(H3_ALPN_DRAFT31); + break; + case QUIC_VER_DRAFT32: + alpn = H3_ALPN_DRAFT32; + alpnlen = str_size(H3_ALPN_DRAFT32); + break; + case NGTCP2_PROTO_VER_V1: + case NGTCP2_PROTO_VER_V2_DRAFT: + alpn = H3_ALPN_V1; + alpnlen = str_size(H3_ALPN_V1); + break; + default: + if (!config.quiet) { + std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" + << version << std::dec << std::endl; + } + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) { + if (std::equal(alpn, alpn + alpnlen, p)) { + *out = p + 1; + *outlen = *p; + return SSL_TLSEXT_ERR_OK; + } + } + + if (!config.quiet) { + std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl; + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +} // namespace + +namespace { +int alpn_select_proto_hq_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl)); + auto h = static_cast<HandlerBase *>(conn_ref->user_data); + const uint8_t *alpn; + size_t alpnlen; + // This should be the negotiated version, but we have not set the + // negotiated version when this callback is called. + auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); + + switch (version) { + case QUIC_VER_DRAFT29: + alpn = HQ_ALPN_DRAFT29; + alpnlen = str_size(HQ_ALPN_DRAFT29); + break; + case QUIC_VER_DRAFT30: + alpn = HQ_ALPN_DRAFT30; + alpnlen = str_size(HQ_ALPN_DRAFT30); + break; + case QUIC_VER_DRAFT31: + alpn = HQ_ALPN_DRAFT31; + alpnlen = str_size(HQ_ALPN_DRAFT31); + break; + case QUIC_VER_DRAFT32: + alpn = HQ_ALPN_DRAFT32; + alpnlen = str_size(HQ_ALPN_DRAFT32); + break; + case NGTCP2_PROTO_VER_V1: + case NGTCP2_PROTO_VER_V2_DRAFT: + alpn = HQ_ALPN_V1; + alpnlen = str_size(HQ_ALPN_V1); + break; + default: + if (!config.quiet) { + std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" + << version << std::dec << std::endl; + } + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) { + if (std::equal(alpn, alpn + alpnlen, p)) { + *out = p + 1; + *outlen = *p; + return SSL_TLSEXT_ERR_OK; + } + } + + if (!config.quiet) { + std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl; + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +} // namespace + +namespace { +int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { + // We don't verify the client certificate. Just request it for the + // testing purpose. + return 1; +} +} // namespace + +namespace { +int gen_ticket_cb(SSL *ssl, void *arg) { + auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl)); + auto h = static_cast<HandlerBase *>(conn_ref->user_data); + auto ver = htonl(ngtcp2_conn_get_negotiated_version(h->conn())); + + if (!SSL_SESSION_set1_ticket_appdata(SSL_get0_session(ssl), &ver, + sizeof(ver))) { + return 0; + } + + return 1; +} +} // namespace + +namespace { +SSL_TICKET_RETURN decrypt_ticket_cb(SSL *ssl, SSL_SESSION *session, + const unsigned char *keyname, + size_t keynamelen, SSL_TICKET_STATUS status, + void *arg) { + switch (status) { + case SSL_TICKET_EMPTY: + case SSL_TICKET_NO_DECRYPT: + return SSL_TICKET_RETURN_IGNORE_RENEW; + } + + uint8_t *pver; + uint32_t ver; + size_t verlen; + + if (!SSL_SESSION_get0_ticket_appdata( + session, reinterpret_cast<void **>(&pver), &verlen) || + verlen != sizeof(ver)) { + switch (status) { + case SSL_TICKET_SUCCESS: + return SSL_TICKET_RETURN_IGNORE; + case SSL_TICKET_SUCCESS_RENEW: + default: + return SSL_TICKET_RETURN_IGNORE_RENEW; + } + } + + memcpy(&ver, pver, sizeof(ver)); + + auto conn_ref = static_cast<ngtcp2_crypto_conn_ref *>(SSL_get_app_data(ssl)); + auto h = static_cast<HandlerBase *>(conn_ref->user_data); + + if (ngtcp2_conn_get_client_chosen_version(h->conn()) != ntohl(ver)) { + switch (status) { + case SSL_TICKET_SUCCESS: + return SSL_TICKET_RETURN_IGNORE; + case SSL_TICKET_SUCCESS_RENEW: + default: + return SSL_TICKET_RETURN_IGNORE_RENEW; + } + } + + switch (status) { + case SSL_TICKET_SUCCESS: + return SSL_TICKET_RETURN_USE; + case SSL_TICKET_SUCCESS_RENEW: + default: + return SSL_TICKET_RETURN_USE_RENEW; + } +} +} // namespace + +int TLSServerContext::init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto) { + constexpr static unsigned char sid_ctx[] = "ngtcp2 server"; + + ssl_ctx_ = SSL_CTX_new(TLS_server_method()); + if (!ssl_ctx_) { + std::cerr << "SSSL_CTX_new: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + if (ngtcp2_crypto_openssl_configure_server_context(ssl_ctx_) != 0) { + std::cerr << "ngtcp2_crypto_openssl_configure_server_context failed" + << std::endl; + return -1; + } + + SSL_CTX_set_max_early_data(ssl_ctx_, UINT32_MAX); + + constexpr auto ssl_opts = (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + SSL_OP_SINGLE_ECDH_USE | + SSL_OP_CIPHER_SERVER_PREFERENCE | + SSL_OP_NO_ANTI_REPLAY; + + SSL_CTX_set_options(ssl_ctx_, ssl_opts); + + if (SSL_CTX_set_ciphersuites(ssl_ctx_, config.ciphers) != 1) { + std::cerr << "SSL_CTX_set_ciphersuites: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_set1_groups_list(ssl_ctx_, config.groups) != 1) { + std::cerr << "SSL_CTX_set1_groups_list failed" << std::endl; + return -1; + } + + SSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS); + + switch (app_proto) { + case AppProtocol::H3: + SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr); + break; + case AppProtocol::HQ: + SSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr); + break; + } + + SSL_CTX_set_default_verify_paths(ssl_ctx_); + + if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, + SSL_FILETYPE_PEM) != 1) { + std::cerr << "SSL_CTX_use_PrivateKey_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) { + std::cerr << "SSL_CTX_use_certificate_chain_file: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (SSL_CTX_check_private_key(ssl_ctx_) != 1) { + std::cerr << "SSL_CTX_check_private_key: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + SSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1); + + if (config.verify_client) { + SSL_CTX_set_verify(ssl_ctx_, + SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_cb); + } + + SSL_CTX_set_session_ticket_cb(ssl_ctx_, gen_ticket_cb, decrypt_ticket_cb, + nullptr); + + return 0; +} + +extern std::ofstream keylog_file; + +namespace { +void keylog_callback(const SSL *ssl, const char *line) { + keylog_file.write(line, strlen(line)); + keylog_file.put('\n'); + keylog_file.flush(); +} +} // namespace + +void TLSServerContext::enable_keylog() { + SSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); +} diff --git a/examples/tls_server_context_openssl.h b/examples/tls_server_context_openssl.h new file mode 100644 index 0000000..94c7561 --- /dev/null +++ b/examples/tls_server_context_openssl.h @@ -0,0 +1,54 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_CONTEXT_OPENSSL_H +#define TLS_SERVER_CONTEXT_OPENSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <openssl/ssl.h> + +#include "shared.h" + +using namespace ngtcp2; + +class TLSServerContext { +public: + TLSServerContext(); + ~TLSServerContext(); + + int init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto); + + SSL_CTX *get_native_handle() const; + + void enable_keylog(); + +private: + SSL_CTX *ssl_ctx_; +}; + +#endif // TLS_SERVER_CONTEXT_OPENSSL_H diff --git a/examples/tls_server_context_picotls.cc b/examples/tls_server_context_picotls.cc new file mode 100644 index 0000000..51d41b6 --- /dev/null +++ b/examples/tls_server_context_picotls.cc @@ -0,0 +1,318 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_context_picotls.h" + +#include <iostream> +#include <memory> + +#include <ngtcp2/ngtcp2_crypto_picotls.h> + +#include <openssl/pem.h> +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +# include <openssl/core_names.h> +#endif // OPENSSL_VERSION_NUMBER >= 0x30000000L + +#include "server_base.h" +#include "template.h" + +extern Config config; + +namespace { +int on_client_hello_cb(ptls_on_client_hello_t *self, ptls_t *ptls, + ptls_on_client_hello_parameters_t *params) { + auto &negprotos = params->negotiated_protocols; + + for (size_t i = 0; i < negprotos.count; ++i) { + auto &proto = negprotos.list[i]; + if (H3_ALPN_V1[0] == proto.len && + memcmp(&H3_ALPN_V1[1], proto.base, proto.len) == 0) { + if (ptls_set_negotiated_protocol( + ptls, reinterpret_cast<char *>(proto.base), proto.len) != 0) { + return -1; + } + + return 0; + } + } + + return PTLS_ALERT_NO_APPLICATION_PROTOCOL; +} + +ptls_on_client_hello_t on_client_hello = {on_client_hello_cb}; +} // namespace + +namespace { +auto ticket_hmac = EVP_sha256(); + +template <size_t N> void random_bytes(std::array<uint8_t, N> &dest) { + ptls_openssl_random_bytes(dest.data(), dest.size()); +} + +const std::array<uint8_t, 16> &get_ticket_key_name() { + static std::array<uint8_t, 16> key_name; + random_bytes(key_name); + return key_name; +} + +const std::array<uint8_t, 32> &get_ticket_key() { + static std::array<uint8_t, 32> key; + random_bytes(key); + return key; +} + +const std::array<uint8_t, 32> &get_ticket_hmac_key() { + static std::array<uint8_t, 32> hmac_key; + random_bytes(hmac_key); + return hmac_key; +} +} // namespace + +namespace { +int ticket_key_cb(unsigned char *key_name, unsigned char *iv, + EVP_CIPHER_CTX *ctx, +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC_CTX *hctx, +#else // OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_CTX *hctx, +#endif // OPENSSL_VERSION_NUMBER < 0x30000000L + int enc) { + static const auto &static_key_name = get_ticket_key_name(); + static const auto &static_key = get_ticket_key(); + static const auto &static_hmac_key = get_ticket_hmac_key(); + + if (enc) { + ptls_openssl_random_bytes(iv, EVP_MAX_IV_LENGTH); + + memcpy(key_name, static_key_name.data(), static_key_name.size()); + + EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, static_key.data(), iv); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + std::array<OSSL_PARAM, 3> params{ + OSSL_PARAM_construct_octet_string( + OSSL_MAC_PARAM_KEY, const_cast<uint8_t *>(static_hmac_key.data()), + static_hmac_key.size()), + OSSL_PARAM_construct_utf8_string( + OSSL_MAC_PARAM_DIGEST, + const_cast<char *>(EVP_MD_get0_name(ticket_hmac)), 0), + OSSL_PARAM_construct_end(), + }; + if (!EVP_MAC_CTX_set_params(hctx, params.data())) { + /* TODO Which value should we return on error? */ + return 0; + } +#else // OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_Init_ex(hctx, static_hmac_key.data(), static_hmac_key.size(), + ticket_hmac, nullptr); +#endif // OPENSSL_VERSION_NUMBER < 0x30000000L + + return 1; + } + + if (memcmp(key_name, static_key_name.data(), static_key_name.size()) != 0) { + return 0; + } + + EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, static_key.data(), iv); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + std::array<OSSL_PARAM, 3> params{ + OSSL_PARAM_construct_octet_string( + OSSL_MAC_PARAM_KEY, const_cast<uint8_t *>(static_hmac_key.data()), + static_hmac_key.size()), + OSSL_PARAM_construct_utf8_string( + OSSL_MAC_PARAM_DIGEST, + const_cast<char *>(EVP_MD_get0_name(ticket_hmac)), 0), + OSSL_PARAM_construct_end(), + }; + if (!EVP_MAC_CTX_set_params(hctx, params.data())) { + /* TODO Which value should we return on error? */ + return 0; + } +#else // OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_Init_ex(hctx, static_hmac_key.data(), static_hmac_key.size(), + ticket_hmac, nullptr); +#endif // OPENSSL_VERSION_NUMBER < 0x30000000L + + return 1; +} +} // namespace + +namespace { +int encrypt_ticket_cb(ptls_encrypt_ticket_t *encrypt_ticket, ptls_t *ptls, + int is_encrypt, ptls_buffer_t *dst, ptls_iovec_t src) { + int rv; + auto conn_ref = + static_cast<ngtcp2_crypto_conn_ref *>(*ptls_get_data_ptr(ptls)); + auto conn = conn_ref->get_conn(conn_ref); + uint32_t ver; + + if (is_encrypt) { + ver = htonl(ngtcp2_conn_get_negotiated_version(conn)); + // TODO Replace std::make_unique with + // std::make_unique_for_overwrite when it is available. + auto buf = std::make_unique<uint8_t[]>(src.len + sizeof(ver)); + auto p = std::copy_n(src.base, src.len, buf.get()); + p = std::copy_n(reinterpret_cast<uint8_t *>(&ver), sizeof(ver), p); + + src.base = buf.get(); + src.len = p - buf.get(); + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + rv = ptls_openssl_encrypt_ticket_evp(dst, src, ticket_key_cb); +#else // OPENSSL_VERSION_NUMBER < 0x30000000L + rv = ptls_openssl_encrypt_ticket(dst, src, ticket_key_cb); +#endif // OPENSSL_VERSION_NUMBER < 0x30000000L + if (rv != 0) { + return -1; + } + + return 0; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + rv = ptls_openssl_decrypt_ticket_evp(dst, src, ticket_key_cb); +#else // OPENSSL_VERSION_NUMBER < 0x30000000L + rv = ptls_openssl_decrypt_ticket(dst, src, ticket_key_cb); +#endif // OPENSSL_VERSION_NUMBER < 0x30000000L + if (rv != 0) { + return -1; + } + + if (dst->off < sizeof(ver)) { + return -1; + } + + memcpy(&ver, dst->base + dst->off - sizeof(ver), sizeof(ver)); + + if (ngtcp2_conn_get_client_chosen_version(conn) != ntohl(ver)) { + return -1; + } + + dst->off -= sizeof(ver); + + return 0; +} + +ptls_encrypt_ticket_t encrypt_ticket = {encrypt_ticket_cb}; +} // namespace + +namespace { +ptls_key_exchange_algorithm_t *key_exchanges[] = { + &ptls_openssl_x25519, + &ptls_openssl_secp256r1, + &ptls_openssl_secp384r1, + &ptls_openssl_secp521r1, + nullptr, +}; +} // namespace + +namespace { +ptls_cipher_suite_t *cipher_suites[] = { + &ptls_openssl_aes128gcmsha256, + &ptls_openssl_aes256gcmsha384, + &ptls_openssl_chacha20poly1305sha256, + nullptr, +}; +} // namespace + +TLSServerContext::TLSServerContext() + : ctx_{ + .random_bytes = ptls_openssl_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = key_exchanges, + .cipher_suites = cipher_suites, + .on_client_hello = &on_client_hello, + .ticket_lifetime = 86400, + .require_dhe_on_psk = 1, + .server_cipher_preference = 1, + .encrypt_ticket = &encrypt_ticket, + }, + sign_cert_{} +{} + +TLSServerContext::~TLSServerContext() { + if (sign_cert_.key) { + ptls_openssl_dispose_sign_certificate(&sign_cert_); + } + + for (size_t i = 0; i < ctx_.certificates.count; ++i) { + free(ctx_.certificates.list[i].base); + } + free(ctx_.certificates.list); +} + +ptls_context_t *TLSServerContext::get_native_handle() { return &ctx_; } + +int TLSServerContext::init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto) { + if (ngtcp2_crypto_picotls_configure_server_context(&ctx_) != 0) { + std::cerr << "ngtcp2_crypto_picotls_configure_server_context failed" + << std::endl; + return -1; + } + + if (ptls_load_certificates(&ctx_, cert_file) != 0) { + std::cerr << "ptls_load_certificates failed" << std::endl; + return -1; + } + + if (load_private_key(private_key_file) != 0) { + return -1; + } + + if (config.verify_client) { + ctx_.require_client_authentication = 1; + } + + return 0; +} + +int TLSServerContext::load_private_key(const char *private_key_file) { + auto fp = fopen(private_key_file, "rb"); + if (fp == nullptr) { + std::cerr << "Could not open private key file " << private_key_file << ": " + << strerror(errno) << std::endl; + return -1; + } + + auto fp_d = defer(fclose, fp); + + auto pkey = PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr); + if (pkey == nullptr) { + std::cerr << "Could not read private key file " << private_key_file + << std::endl; + return -1; + } + + auto pkey_d = defer(EVP_PKEY_free, pkey); + + if (ptls_openssl_init_sign_certificate(&sign_cert_, pkey) != 0) { + std::cerr << "ptls_openssl_init_sign_certificate failed" << std::endl; + return -1; + } + + ctx_.sign_certificate = &sign_cert_.super; + + return 0; +} diff --git a/examples/tls_server_context_picotls.h b/examples/tls_server_context_picotls.h new file mode 100644 index 0000000..c9dc489 --- /dev/null +++ b/examples/tls_server_context_picotls.h @@ -0,0 +1,59 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_CONTEXT_PICOTLS_H +#define TLS_SERVER_CONTEXT_PICOTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <picotls.h> +#include <picotls/openssl.h> + +#include "shared.h" + +using namespace ngtcp2; + +class TLSServerContext { +public: + TLSServerContext(); + ~TLSServerContext(); + + int init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto); + + ptls_context_t *get_native_handle(); + + // TODO Implement keylog. + void enable_keylog() {} + +private: + int load_private_key(const char *private_key_file); + + ptls_context_t ctx_; + ptls_openssl_sign_certificate_t sign_cert_; +}; + +#endif // TLS_SERVER_CONTEXT_PICOTLS_H diff --git a/examples/tls_server_context_wolfssl.cc b/examples/tls_server_context_wolfssl.cc new file mode 100644 index 0000000..ed09b72 --- /dev/null +++ b/examples/tls_server_context_wolfssl.cc @@ -0,0 +1,284 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_context_wolfssl.h" + +#include <iostream> +#include <fstream> +#include <limits> + +#include <ngtcp2/ngtcp2_crypto_wolfssl.h> + +#include "server_base.h" +#include "template.h" + +extern Config config; + +TLSServerContext::TLSServerContext() : ssl_ctx_{nullptr} {} + +TLSServerContext::~TLSServerContext() { + if (ssl_ctx_) { + wolfSSL_CTX_free(ssl_ctx_); + } +} + +WOLFSSL_CTX *TLSServerContext::get_native_handle() const { return ssl_ctx_; } + +namespace { +int alpn_select_proto_h3_cb(WOLFSSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto conn_ref = + static_cast<ngtcp2_crypto_conn_ref *>(wolfSSL_get_app_data(ssl)); + auto h = static_cast<HandlerBase *>(conn_ref->user_data); + const uint8_t *alpn; + size_t alpnlen; + // This should be the negotiated version, but we have not set the + // negotiated version when this callback is called. + auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); + + switch (version) { + case QUIC_VER_DRAFT29: + alpn = H3_ALPN_DRAFT29; + alpnlen = str_size(H3_ALPN_DRAFT29); + break; + case QUIC_VER_DRAFT30: + alpn = H3_ALPN_DRAFT30; + alpnlen = str_size(H3_ALPN_DRAFT30); + break; + case QUIC_VER_DRAFT31: + alpn = H3_ALPN_DRAFT31; + alpnlen = str_size(H3_ALPN_DRAFT31); + break; + case QUIC_VER_DRAFT32: + alpn = H3_ALPN_DRAFT32; + alpnlen = str_size(H3_ALPN_DRAFT32); + break; + case NGTCP2_PROTO_VER_V1: + case NGTCP2_PROTO_VER_V2_DRAFT: + alpn = H3_ALPN_V1; + alpnlen = str_size(H3_ALPN_V1); + break; + default: + if (!config.quiet) { + std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" + << version << std::dec << std::endl; + } + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) { + if (std::equal(alpn, alpn + alpnlen, p)) { + *out = p + 1; + *outlen = *p; + return SSL_TLSEXT_ERR_OK; + } + } + + if (!config.quiet) { + std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl; + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +} // namespace + +namespace { +int alpn_select_proto_hq_cb(WOLFSSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + auto conn_ref = + static_cast<ngtcp2_crypto_conn_ref *>(wolfSSL_get_app_data(ssl)); + auto h = static_cast<HandlerBase *>(conn_ref->user_data); + const uint8_t *alpn; + size_t alpnlen; + // This should be the negotiated version, but we have not set the + // negotiated version when this callback is called. + auto version = ngtcp2_conn_get_client_chosen_version(h->conn()); + + switch (version) { + case QUIC_VER_DRAFT29: + alpn = HQ_ALPN_DRAFT29; + alpnlen = str_size(HQ_ALPN_DRAFT29); + break; + case QUIC_VER_DRAFT30: + alpn = HQ_ALPN_DRAFT30; + alpnlen = str_size(HQ_ALPN_DRAFT30); + break; + case QUIC_VER_DRAFT31: + alpn = HQ_ALPN_DRAFT31; + alpnlen = str_size(HQ_ALPN_DRAFT31); + break; + case QUIC_VER_DRAFT32: + alpn = HQ_ALPN_DRAFT32; + alpnlen = str_size(HQ_ALPN_DRAFT32); + break; + case NGTCP2_PROTO_VER_V1: + case NGTCP2_PROTO_VER_V2_DRAFT: + alpn = HQ_ALPN_V1; + alpnlen = str_size(HQ_ALPN_V1); + break; + default: + if (!config.quiet) { + std::cerr << "Unexpected quic protocol version: " << std::hex << "0x" + << version << std::dec << std::endl; + } + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + for (auto p = in, end = in + inlen; p + alpnlen <= end; p += *p + 1) { + if (std::equal(alpn, alpn + alpnlen, p)) { + *out = p + 1; + *outlen = *p; + return SSL_TLSEXT_ERR_OK; + } + } + + if (!config.quiet) { + std::cerr << "Client did not present ALPN " << &alpn[1] << std::endl; + } + + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +} // namespace + +namespace { +int verify_cb(int preverify_ok, X509_STORE_CTX *ctx) { + // We don't verify the client certificate. Just request it for the + // testing purpose. + return 1; +} +} // namespace + +int TLSServerContext::init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto) { + constexpr static unsigned char sid_ctx[] = "ngtcp2 server"; + +#if defined(DEBUG_WOLFSSL) + if (!config.quiet) { + /*wolfSSL_Debugging_ON();*/ + } +#endif + + ssl_ctx_ = wolfSSL_CTX_new(wolfTLSv1_3_server_method()); + if (!ssl_ctx_) { + std::cerr << "wolfSSL_CTX_new: " + << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + if (ngtcp2_crypto_wolfssl_configure_server_context(ssl_ctx_) != 0) { + std::cerr << "ngtcp2_crypto_wolfssl_configure_server_context failed" + << std::endl; + return -1; + } + +#ifdef WOLFSSL_EARLY_DATA + wolfSSL_CTX_set_max_early_data(ssl_ctx_, UINT32_MAX); +#endif + + constexpr auto ssl_opts = + (WOLFSSL_OP_ALL & ~WOLFSSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | + WOLFSSL_OP_SINGLE_ECDH_USE | WOLFSSL_OP_CIPHER_SERVER_PREFERENCE; + + wolfSSL_CTX_set_options(ssl_ctx_, ssl_opts); + + if (wolfSSL_CTX_set_cipher_list(ssl_ctx_, config.ciphers) != 1) { + std::cerr << "wolfSSL_CTX_set_cipher_list: " + << ERR_error_string(ERR_get_error(), nullptr) << std::endl; + return -1; + } + + if (wolfSSL_CTX_set1_curves_list(ssl_ctx_, + const_cast<char *>(config.groups)) != 1) { + std::cerr << "wolfSSL_CTX_set1_curves_list(" << config.groups << ") failed" + << std::endl; + return -1; + } + + wolfSSL_CTX_set_mode(ssl_ctx_, SSL_MODE_RELEASE_BUFFERS); + + switch (app_proto) { + case AppProtocol::H3: + wolfSSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_h3_cb, nullptr); + break; + case AppProtocol::HQ: + wolfSSL_CTX_set_alpn_select_cb(ssl_ctx_, alpn_select_proto_hq_cb, nullptr); + break; + } + + wolfSSL_CTX_set_default_verify_paths(ssl_ctx_); + + if (wolfSSL_CTX_use_PrivateKey_file(ssl_ctx_, private_key_file, + SSL_FILETYPE_PEM) != 1) { + std::cerr << "wolfSSL_CTX_use_PrivateKey_file: " + << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + if (wolfSSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file) != 1) { + std::cerr << "wolfSSL_CTX_use_certificate_chain_file: " + << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + if (wolfSSL_CTX_check_private_key(ssl_ctx_) != 1) { + std::cerr << "wolfSSL_CTX_check_private_key: " + << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + wolfSSL_CTX_set_session_id_context(ssl_ctx_, sid_ctx, sizeof(sid_ctx) - 1); + + if (config.verify_client) { + wolfSSL_CTX_set_verify(ssl_ctx_, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_CLIENT_ONCE | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, + verify_cb); + } + + return 0; +} + +extern std::ofstream keylog_file; + +#ifdef HAVE_SECRET_CALLBACK +namespace { +void keylog_callback(const WOLFSSL *ssl, const char *line) { + keylog_file.write(line, strlen(line)); + keylog_file.put('\n'); + keylog_file.flush(); +} +} // namespace +#endif + +void TLSServerContext::enable_keylog() { +#ifdef HAVE_SECRET_CALLBACK + wolfSSL_CTX_set_keylog_callback(ssl_ctx_, keylog_callback); +#endif +} diff --git a/examples/tls_server_context_wolfssl.h b/examples/tls_server_context_wolfssl.h new file mode 100644 index 0000000..e0b3c38 --- /dev/null +++ b/examples/tls_server_context_wolfssl.h @@ -0,0 +1,55 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_CONTEXT_WOLFSSL_H +#define TLS_SERVER_CONTEXT_WOLFSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> + +#include "shared.h" + +using namespace ngtcp2; + +class TLSServerContext { +public: + TLSServerContext(); + ~TLSServerContext(); + + int init(const char *private_key_file, const char *cert_file, + AppProtocol app_proto); + + WOLFSSL_CTX *get_native_handle() const; + + void enable_keylog(); + +private: + WOLFSSL_CTX *ssl_ctx_; +}; + +#endif // TLS_SERVER_CONTEXT_WOLFSSL_H diff --git a/examples/tls_server_session.h b/examples/tls_server_session.h new file mode 100644 index 0000000..85b76e4 --- /dev/null +++ b/examples/tls_server_session.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_SESSION_H +#define TLS_SERVER_SESSION_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#if defined(ENABLE_EXAMPLE_OPENSSL) && defined(WITH_EXAMPLE_OPENSSL) +# include "tls_server_session_openssl.h" +#endif // ENABLE_EXAMPLE_OPENSSL && WITH_EXAMPLE_OPENSSL + +#if defined(ENABLE_EXAMPLE_GNUTLS) && defined(WITH_EXAMPLE_GNUTLS) +# include "tls_server_session_gnutls.h" +#endif // ENABLE_EXAMPLE_GNUTLS && WITH_EXAMPLE_GNUTLS + +#if defined(ENABLE_EXAMPLE_BORINGSSL) && defined(WITH_EXAMPLE_BORINGSSL) +# include "tls_server_session_boringssl.h" +#endif // ENABLE_EXAMPLE_BORINGSSL && WITH_EXAMPLE_BORINGSSL + +#if defined(ENABLE_EXAMPLE_PICOTLS) && defined(WITH_EXAMPLE_PICOTLS) +# include "tls_server_session_picotls.h" +#endif // ENABLE_EXAMPLE_PICOTLS && WITH_EXAMPLE_PICOTLS + +#if defined(ENABLE_EXAMPLE_WOLFSSL) && defined(WITH_EXAMPLE_WOLFSSL) +# include "tls_server_session_wolfssl.h" +#endif // ENABLE_EXAMPLE_WOLFSSL && WITH_EXAMPLE_WOLFSSL + +#endif // TLS_SERVER_SESSION_H diff --git a/examples/tls_server_session_boringssl.cc b/examples/tls_server_session_boringssl.cc new file mode 100644 index 0000000..133f4d0 --- /dev/null +++ b/examples/tls_server_session_boringssl.cc @@ -0,0 +1,84 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_session_boringssl.h" + +#include <cassert> +#include <iostream> + +#include <ngtcp2/ngtcp2.h> + +#include "tls_server_context_boringssl.h" +#include "server_base.h" + +extern Config config; + +TLSServerSession::TLSServerSession() {} + +TLSServerSession::~TLSServerSession() {} + +int TLSServerSession::init(const TLSServerContext &tls_ctx, + HandlerBase *handler) { + auto ssl_ctx = tls_ctx.get_native_handle(); + + ssl_ = SSL_new(ssl_ctx); + if (!ssl_) { + std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + SSL_set_app_data(ssl_, handler->conn_ref()); + SSL_set_accept_state(ssl_); + SSL_set_early_data_enabled(ssl_, 1); + SSL_set_quic_use_legacy_codepoint(ssl_, 0); + + std::array<uint8_t, 128> quic_early_data_ctx; + ngtcp2_transport_params params; + memset(¶ms, 0, sizeof(params)); + params.initial_max_streams_bidi = config.max_streams_bidi; + params.initial_max_streams_uni = config.max_streams_uni; + params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; + params.initial_max_stream_data_bidi_remote = + config.max_stream_data_bidi_remote; + params.initial_max_stream_data_uni = config.max_stream_data_uni; + params.initial_max_data = config.max_data; + + auto quic_early_data_ctxlen = ngtcp2_encode_transport_params( + quic_early_data_ctx.data(), quic_early_data_ctx.size(), + NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, ¶ms); + if (quic_early_data_ctxlen < 0) { + std::cerr << "ngtcp2_encode_transport_params: " + << ngtcp2_strerror(quic_early_data_ctxlen) << std::endl; + return -1; + } + + if (SSL_set_quic_early_data_context(ssl_, quic_early_data_ctx.data(), + quic_early_data_ctxlen) != 1) { + std::cerr << "SSL_set_quic_early_data_context failed" << std::endl; + return -1; + } + + return 0; +} diff --git a/examples/tls_server_session_boringssl.h b/examples/tls_server_session_boringssl.h new file mode 100644 index 0000000..eebf18a --- /dev/null +++ b/examples/tls_server_session_boringssl.h @@ -0,0 +1,47 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_SESSION_BORINGSSL_H +#define TLS_SERVER_SESSION_BORINGSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_openssl.h" + +class TLSServerContext; +class HandlerBase; + +class TLSServerSession : public TLSSessionBase { +public: + TLSServerSession(); + ~TLSServerSession(); + + int init(const TLSServerContext &tls_ctx, HandlerBase *handler); + // ticket is sent automatically. + int send_session_ticket() { return 0; } +}; + +#endif // TLS_SERVER_SESSION_BORINGSSL_H diff --git a/examples/tls_server_session_gnutls.cc b/examples/tls_server_session_gnutls.cc new file mode 100644 index 0000000..ee776c4 --- /dev/null +++ b/examples/tls_server_session_gnutls.cc @@ -0,0 +1,155 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_session_gnutls.h" + +#include <cassert> +#include <iostream> +#include <fstream> +#include <array> + +#include <ngtcp2/ngtcp2_crypto_gnutls.h> + +#include "tls_server_context_gnutls.h" +#include "server_base.h" +#include "util.h" + +// Based on https://github.com/ueno/ngtcp2-gnutls-examples + +using namespace ngtcp2; + +extern Config config; + +TLSServerSession::TLSServerSession() {} + +TLSServerSession::~TLSServerSession() {} + +namespace { +int client_hello_cb(gnutls_session_t session, unsigned int htype, unsigned when, + unsigned int incoming, const gnutls_datum_t *msg) { + assert(htype == GNUTLS_HANDSHAKE_CLIENT_HELLO); + assert(when == GNUTLS_HOOK_POST); + assert(incoming == 1); + + // check if ALPN extension is present and properly selected h3 + gnutls_datum_t alpn; + if (auto rv = gnutls_alpn_get_selected_protocol(session, &alpn); rv != 0) { + return rv; + } + + // TODO Fix this to properly select ALPN based on app_proto. + + // strip the first byte from H3_ALPN_V1 + auto h3 = reinterpret_cast<const char *>(&H3_ALPN_V1[1]); + if (static_cast<size_t>(H3_ALPN_V1[0]) != alpn.size || + !std::equal(alpn.data, alpn.data + alpn.size, h3)) { + return -1; + } + + return 0; +} +} // namespace + +int TLSServerSession::init(const TLSServerContext &tls_ctx, + HandlerBase *handler) { + if (auto rv = + gnutls_init(&session_, GNUTLS_SERVER | GNUTLS_ENABLE_EARLY_DATA | + GNUTLS_NO_AUTO_SEND_TICKET | + GNUTLS_NO_END_OF_EARLY_DATA); + rv != 0) { + std::cerr << "gnutls_init failed: " << gnutls_strerror(rv) << std::endl; + return -1; + } + + std::string priority = "%DISABLE_TLS13_COMPAT_MODE:"; + priority += config.ciphers; + priority += ':'; + priority += config.groups; + + if (auto rv = gnutls_priority_set_direct(session_, priority.c_str(), nullptr); + rv != 0) { + std::cerr << "gnutls_priority_set_direct failed: " << gnutls_strerror(rv) + << std::endl; + return -1; + } + + auto rv = gnutls_session_ticket_enable_server( + session_, tls_ctx.get_session_ticket_key()); + if (rv != 0) { + std::cerr << "gnutls_session_ticket_enable_server failed: " + << gnutls_strerror(rv) << std::endl; + return -1; + } + + gnutls_handshake_set_hook_function(session_, GNUTLS_HANDSHAKE_CLIENT_HELLO, + GNUTLS_HOOK_POST, client_hello_cb); + + if (ngtcp2_crypto_gnutls_configure_server_session(session_) != 0) { + std::cerr << "ngtcp2_crypto_gnutls_configure_server_session failed" + << std::endl; + return -1; + } + + gnutls_anti_replay_enable(session_, tls_ctx.get_anti_replay()); + + gnutls_record_set_max_early_data_size(session_, 0xffffffffu); + + gnutls_session_set_ptr(session_, handler->conn_ref()); + + if (auto rv = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, + tls_ctx.get_certificate_credentials()); + rv != 0) { + std::cerr << "gnutls_credentials_set failed: " << gnutls_strerror(rv) + << std::endl; + return -1; + } + + // TODO Set all available ALPN based on app_proto. + + // strip the first byte from H3_ALPN_V1 + gnutls_datum_t alpn{ + .data = const_cast<uint8_t *>(&H3_ALPN_V1[1]), + .size = H3_ALPN_V1[0], + }; + gnutls_alpn_set_protocols(session_, &alpn, 1, + GNUTLS_ALPN_MANDATORY | + GNUTLS_ALPN_SERVER_PRECEDENCE); + + if (config.verify_client) { + gnutls_certificate_server_set_request(session_, GNUTLS_CERT_REQUIRE); + gnutls_certificate_send_x509_rdn_sequence(session_, 1); + } + + return 0; +} + +int TLSServerSession::send_session_ticket() { + if (auto rv = gnutls_session_ticket_send(session_, 1, 0); rv != 0) { + std::cerr << "gnutls_session_ticket_send failed: " << gnutls_strerror(rv) + << std::endl; + return -1; + } + + return 0; +} diff --git a/examples/tls_server_session_gnutls.h b/examples/tls_server_session_gnutls.h new file mode 100644 index 0000000..994c643 --- /dev/null +++ b/examples/tls_server_session_gnutls.h @@ -0,0 +1,46 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_SESSION_GNUTLS_H +#define TLS_SERVER_SESSION_GNUTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_gnutls.h" + +class TLSServerContext; +class HandlerBase; + +class TLSServerSession : public TLSSessionBase { +public: + TLSServerSession(); + ~TLSServerSession(); + + int init(const TLSServerContext &tls_ctx, HandlerBase *handler); + int send_session_ticket(); +}; + +#endif // TLS_SERVER_SESSION_GNUTLS_H diff --git a/examples/tls_server_session_openssl.cc b/examples/tls_server_session_openssl.cc new file mode 100644 index 0000000..5e93e41 --- /dev/null +++ b/examples/tls_server_session_openssl.cc @@ -0,0 +1,54 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_session_openssl.h" + +#include <iostream> + +#include <openssl/err.h> + +#include "tls_server_context_openssl.h" +#include "server_base.h" + +TLSServerSession::TLSServerSession() {} + +TLSServerSession::~TLSServerSession() {} + +int TLSServerSession::init(const TLSServerContext &tls_ctx, + HandlerBase *handler) { + auto ssl_ctx = tls_ctx.get_native_handle(); + + ssl_ = SSL_new(ssl_ctx); + if (!ssl_) { + std::cerr << "SSL_new: " << ERR_error_string(ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + SSL_set_app_data(ssl_, handler->conn_ref()); + SSL_set_accept_state(ssl_); + SSL_set_quic_early_data_enabled(ssl_, 1); + + return 0; +} diff --git a/examples/tls_server_session_openssl.h b/examples/tls_server_session_openssl.h new file mode 100644 index 0000000..ef84b39 --- /dev/null +++ b/examples/tls_server_session_openssl.h @@ -0,0 +1,47 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_SESSION_OPENSSL_H +#define TLS_SERVER_SESSION_OPENSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_openssl.h" + +class TLSServerContext; +class HandlerBase; + +class TLSServerSession : public TLSSessionBase { +public: + TLSServerSession(); + ~TLSServerSession(); + + int init(const TLSServerContext &tls_ctx, HandlerBase *handler); + // ticket is sent automatically. + int send_session_ticket() { return 0; } +}; + +#endif // TLS_SERVER_SESSION_OPENSSL_H diff --git a/examples/tls_server_session_picotls.cc b/examples/tls_server_session_picotls.cc new file mode 100644 index 0000000..f1124aa --- /dev/null +++ b/examples/tls_server_session_picotls.cc @@ -0,0 +1,70 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_session_picotls.h" + +#include <cassert> +#include <iostream> + +#include <ngtcp2/ngtcp2_crypto_picotls.h> + +#include "tls_server_context_picotls.h" +#include "server_base.h" +#include "util.h" + +using namespace ngtcp2; + +extern Config config; + +TLSServerSession::TLSServerSession() {} + +TLSServerSession::~TLSServerSession() {} + +int TLSServerSession::init(TLSServerContext &tls_ctx, HandlerBase *handler) { + cptls_.ptls = ptls_server_new(tls_ctx.get_native_handle()); + if (!cptls_.ptls) { + std::cerr << "ptls_server_new failed" << std::endl; + return -1; + } + + *ptls_get_data_ptr(cptls_.ptls) = handler->conn_ref(); + + cptls_.handshake_properties.additional_extensions = + new ptls_raw_extension_t[2]{ + { + .type = UINT16_MAX, + }, + { + .type = UINT16_MAX, + }, + }; + + if (ngtcp2_crypto_picotls_configure_server_session(&cptls_) != 0) { + std::cerr << "ngtcp2_crypto_picotls_configure_server_session failed" + << std::endl; + return -1; + } + + return 0; +} diff --git a/examples/tls_server_session_picotls.h b/examples/tls_server_session_picotls.h new file mode 100644 index 0000000..ea919a2 --- /dev/null +++ b/examples/tls_server_session_picotls.h @@ -0,0 +1,47 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_SESSION_PICOTLS_H +#define TLS_SERVER_SESSION_PICOTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_picotls.h" + +class TLSServerContext; +class HandlerBase; + +class TLSServerSession : public TLSSessionBase { +public: + TLSServerSession(); + ~TLSServerSession(); + + int init(TLSServerContext &tls_ctx, HandlerBase *handler); + // ticket is sent automatically. + int send_session_ticket() { return 0; } +}; + +#endif // TLS_SERVER_SESSION_PICOTLS_H diff --git a/examples/tls_server_session_wolfssl.cc b/examples/tls_server_session_wolfssl.cc new file mode 100644 index 0000000..68497ad --- /dev/null +++ b/examples/tls_server_session_wolfssl.cc @@ -0,0 +1,55 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_server_session_wolfssl.h" + +#include <iostream> + +#include "tls_server_context_wolfssl.h" +#include "server_base.h" + +TLSServerSession::TLSServerSession() {} + +TLSServerSession::~TLSServerSession() {} + +int TLSServerSession::init(const TLSServerContext &tls_ctx, + HandlerBase *handler) { + auto ssl_ctx = tls_ctx.get_native_handle(); + + ssl_ = wolfSSL_new(ssl_ctx); + if (!ssl_) { + std::cerr << "wolfSSL_new: " + << wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr) + << std::endl; + return -1; + } + + wolfSSL_set_app_data(ssl_, handler->conn_ref()); + wolfSSL_set_accept_state(ssl_); +#ifdef WOLFSSL_EARLY_DATA + wolfSSL_set_quic_early_data_enabled(ssl_, 1); +#endif + + return 0; +} diff --git a/examples/tls_server_session_wolfssl.h b/examples/tls_server_session_wolfssl.h new file mode 100644 index 0000000..db32441 --- /dev/null +++ b/examples/tls_server_session_wolfssl.h @@ -0,0 +1,47 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SERVER_SESSION_WOLFSSL_H +#define TLS_SERVER_SESSION_WOLFSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include "tls_session_base_wolfssl.h" + +class TLSServerContext; +class HandlerBase; + +class TLSServerSession : public TLSSessionBase { +public: + TLSServerSession(); + ~TLSServerSession(); + + int init(const TLSServerContext &tls_ctx, HandlerBase *handler); + // ticket is sent automatically. + int send_session_ticket() { return 0; } +}; + +#endif // TLS_SERVER_SESSION_WOLFSSL_H diff --git a/examples/tls_session_base_gnutls.cc b/examples/tls_session_base_gnutls.cc new file mode 100644 index 0000000..51460e9 --- /dev/null +++ b/examples/tls_session_base_gnutls.cc @@ -0,0 +1,87 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_session_base_gnutls.h" + +#include <fstream> + +#include "util.h" + +// Based on https://github.com/ueno/ngtcp2-gnutls-examples + +using namespace ngtcp2; + +TLSSessionBase::TLSSessionBase() : session_{nullptr} {} + +TLSSessionBase::~TLSSessionBase() { gnutls_deinit(session_); } + +gnutls_session_t TLSSessionBase::get_native_handle() const { return session_; } + +std::string TLSSessionBase::get_cipher_name() const { + return gnutls_cipher_get_name(gnutls_cipher_get(session_)); +} + +std::string TLSSessionBase::get_selected_alpn() const { + gnutls_datum_t alpn; + + if (auto rv = gnutls_alpn_get_selected_protocol(session_, &alpn); rv == 0) { + return std::string{alpn.data, alpn.data + alpn.size}; + } + + return {}; +} + +extern std::ofstream keylog_file; + +namespace { +int keylog_callback(gnutls_session_t session, const char *label, + const gnutls_datum_t *secret) { + keylog_file.write(label, strlen(label)); + keylog_file.put(' '); + + gnutls_datum_t crandom; + gnutls_datum_t srandom; + + gnutls_session_get_random(session, &crandom, &srandom); + if (crandom.size != 32) { + return -1; + } + + auto crandom_hex = + util::format_hex(reinterpret_cast<unsigned char *>(crandom.data), 32); + keylog_file << crandom_hex << " "; + + auto secret_hex = util::format_hex( + reinterpret_cast<unsigned char *>(secret->data), secret->size); + keylog_file << secret_hex << " "; + + keylog_file.put('\n'); + keylog_file.flush(); + return 0; +} +} // namespace + +void TLSSessionBase::enable_keylog() { + gnutls_session_set_keylog_function(session_, keylog_callback); +} diff --git a/examples/tls_session_base_gnutls.h b/examples/tls_session_base_gnutls.h new file mode 100644 index 0000000..7a23418 --- /dev/null +++ b/examples/tls_session_base_gnutls.h @@ -0,0 +1,51 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SESSION_BASE_GNUTLS_H +#define TLS_SESSION_BASE_GNUTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <string> + +#include <gnutls/gnutls.h> + +class TLSSessionBase { +public: + TLSSessionBase(); + ~TLSSessionBase(); + + gnutls_session_t get_native_handle() const; + + std::string get_cipher_name() const; + std::string get_selected_alpn() const; + void enable_keylog(); + +protected: + gnutls_session_t session_; +}; + +#endif // TLS_SESSION_BASE_GNUTLS_H diff --git a/examples/tls_session_base_openssl.cc b/examples/tls_session_base_openssl.cc new file mode 100644 index 0000000..2de47dc --- /dev/null +++ b/examples/tls_session_base_openssl.cc @@ -0,0 +1,54 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_session_base_openssl.h" + +#include <array> + +#include "util.h" + +using namespace ngtcp2; + +TLSSessionBase::TLSSessionBase() : ssl_{nullptr} {} + +TLSSessionBase::~TLSSessionBase() { + if (ssl_) { + SSL_free(ssl_); + } +} + +SSL *TLSSessionBase::get_native_handle() const { return ssl_; } + +std::string TLSSessionBase::get_cipher_name() const { + return SSL_get_cipher_name(ssl_); +} + +std::string TLSSessionBase::get_selected_alpn() const { + const unsigned char *alpn = nullptr; + unsigned int alpnlen; + + SSL_get0_alpn_selected(ssl_, &alpn, &alpnlen); + + return std::string{alpn, alpn + alpnlen}; +} diff --git a/examples/tls_session_base_openssl.h b/examples/tls_session_base_openssl.h new file mode 100644 index 0000000..ee63b39 --- /dev/null +++ b/examples/tls_session_base_openssl.h @@ -0,0 +1,52 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SESSION_BASE_OPENSSL_H +#define TLS_SESSION_BASE_OPENSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <string> + +#include <openssl/ssl.h> + +class TLSSessionBase { +public: + TLSSessionBase(); + ~TLSSessionBase(); + + SSL *get_native_handle() const; + + std::string get_cipher_name() const; + std::string get_selected_alpn() const; + // Keylog is enabled per SSL_CTX. + void enable_keylog() {} + +protected: + SSL *ssl_; +}; + +#endif // TLS_SESSION_BASE_OPENSSL_H diff --git a/examples/tls_session_base_picotls.cc b/examples/tls_session_base_picotls.cc new file mode 100644 index 0000000..b8413b4 --- /dev/null +++ b/examples/tls_session_base_picotls.cc @@ -0,0 +1,56 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_session_base_picotls.h" + +TLSSessionBase::TLSSessionBase() { ngtcp2_crypto_picotls_ctx_init(&cptls_); } + +TLSSessionBase::~TLSSessionBase() { + ngtcp2_crypto_picotls_deconfigure_session(&cptls_); + + delete[] cptls_.handshake_properties.additional_extensions; + + if (cptls_.ptls) { + ptls_free(cptls_.ptls); + } +} + +ngtcp2_crypto_picotls_ctx *TLSSessionBase::get_native_handle() { + return &cptls_; +} + +std::string TLSSessionBase::get_cipher_name() const { + auto cs = ptls_get_cipher(cptls_.ptls); + return cs->aead->name; +} + +std::string TLSSessionBase::get_selected_alpn() const { + auto alpn = ptls_get_negotiated_protocol(cptls_.ptls); + + if (!alpn) { + return {}; + } + + return alpn; +} diff --git a/examples/tls_session_base_picotls.h b/examples/tls_session_base_picotls.h new file mode 100644 index 0000000..e59ccbc --- /dev/null +++ b/examples/tls_session_base_picotls.h @@ -0,0 +1,54 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SESSION_BASE_PICOTLS_H +#define TLS_SESSION_BASE_PICOTLS_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <string> + +#include <ngtcp2/ngtcp2_crypto_picotls.h> + +#include <picotls.h> + +class TLSSessionBase { +public: + TLSSessionBase(); + ~TLSSessionBase(); + + ngtcp2_crypto_picotls_ctx *get_native_handle(); + + std::string get_cipher_name() const; + std::string get_selected_alpn() const; + // TODO make keylog work with picotls + void enable_keylog(){}; + +protected: + ngtcp2_crypto_picotls_ctx cptls_; +}; + +#endif // TLS_SESSION_BASE_PICOTLS_H diff --git a/examples/tls_session_base_wolfssl.cc b/examples/tls_session_base_wolfssl.cc new file mode 100644 index 0000000..4620182 --- /dev/null +++ b/examples/tls_session_base_wolfssl.cc @@ -0,0 +1,54 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "tls_session_base_wolfssl.h" + +#include <array> + +#include "util.h" + +using namespace ngtcp2; + +TLSSessionBase::TLSSessionBase() : ssl_{nullptr} {} + +TLSSessionBase::~TLSSessionBase() { + if (ssl_) { + wolfSSL_free(ssl_); + } +} + +WOLFSSL *TLSSessionBase::get_native_handle() const { return ssl_; } + +std::string TLSSessionBase::get_cipher_name() const { + return wolfSSL_get_cipher_name(ssl_); +} + +std::string TLSSessionBase::get_selected_alpn() const { + char *alpn = nullptr; + unsigned short alpnlen; + + wolfSSL_ALPN_GetProtocol(ssl_, &alpn, &alpnlen); + + return std::string{alpn, alpn + alpnlen}; +} diff --git a/examples/tls_session_base_wolfssl.h b/examples/tls_session_base_wolfssl.h new file mode 100644 index 0000000..c61eee8 --- /dev/null +++ b/examples/tls_session_base_wolfssl.h @@ -0,0 +1,54 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef TLS_SESSION_BASE_WOLFSSL_H +#define TLS_SESSION_BASE_WOLFSSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <string> + +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> +#include <wolfssl/quic.h> + +class TLSSessionBase { +public: + TLSSessionBase(); + ~TLSSessionBase(); + + WOLFSSL *get_native_handle() const; + + std::string get_cipher_name() const; + std::string get_selected_alpn() const; + // Keylog is enabled per SSL_CTX. + void enable_keylog() {} + +protected: + WOLFSSL *ssl_; +}; + +#endif // TLS_SESSION_BASE_WOLFSSL_H diff --git a/examples/util.cc b/examples/util.cc new file mode 100644 index 0000000..f8401d4 --- /dev/null +++ b/examples/util.cc @@ -0,0 +1,646 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util.h" + +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif // HAVE_ARPA_INET_H +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <netdb.h> + +#include <cassert> +#include <cstring> +#include <chrono> +#include <array> +#include <iostream> +#include <fstream> +#include <algorithm> +#include <limits> +#include <charconv> + +#include "template.h" + +using namespace std::literals; + +namespace ngtcp2 { + +namespace util { + +namespace { +constexpr char LOWER_XDIGITS[] = "0123456789abcdef"; +} // namespace + +std::string format_hex(uint8_t c) { + std::string s; + s.resize(2); + + s[0] = LOWER_XDIGITS[c >> 4]; + s[1] = LOWER_XDIGITS[c & 0xf]; + + return s; +} + +std::string format_hex(const uint8_t *s, size_t len) { + std::string res; + res.resize(len * 2); + + for (size_t i = 0; i < len; ++i) { + auto c = s[i]; + + res[i * 2] = LOWER_XDIGITS[c >> 4]; + res[i * 2 + 1] = LOWER_XDIGITS[c & 0x0f]; + } + return res; +} + +std::string format_hex(const std::string_view &s) { + return format_hex(reinterpret_cast<const uint8_t *>(s.data()), s.size()); +} + +std::string decode_hex(const std::string_view &s) { + assert(s.size() % 2 == 0); + std::string res(s.size() / 2, '0'); + auto p = std::begin(res); + for (auto it = std::begin(s); it != std::end(s); it += 2) { + *p++ = (hex_to_uint(*it) << 4) | hex_to_uint(*(it + 1)); + } + return res; +} + +namespace { +// format_fraction2 formats |n| as fraction part of integer. |n| is +// considered as fraction, and its precision is 3 digits. The last +// digit is ignored. The precision of the resulting fraction is 2 +// digits. +std::string format_fraction2(uint32_t n) { + n /= 10; + + if (n < 10) { + return {'.', '0', static_cast<char>('0' + n)}; + } + return {'.', static_cast<char>('0' + n / 10), + static_cast<char>('0' + (n % 10))}; +} +} // namespace + +namespace { +// round2even rounds the last digit of |n| so that the n / 10 becomes +// even. +uint64_t round2even(uint64_t n) { + if (n % 10 == 5) { + if ((n / 10) & 1) { + n += 10; + } + } else { + n += 5; + } + return n; +} +} // namespace + +std::string format_durationf(uint64_t ns) { + static constexpr const std::string_view units[] = {"us"sv, "ms"sv, "s"sv}; + if (ns < 1000) { + return format_uint(ns) + "ns"; + } + auto unit = 0; + if (ns < 1000000) { + // do nothing + } else if (ns < 1000000000) { + ns /= 1000; + unit = 1; + } else { + ns /= 1000000; + unit = 2; + } + + ns = round2even(ns); + + if (ns / 1000 >= 1000 && unit < 2) { + ns /= 1000; + ++unit; + } + + auto res = format_uint(ns / 1000); + res += format_fraction2(ns % 1000); + res += units[unit]; + + return res; +} + +std::mt19937 make_mt19937() { + std::random_device rd; + return std::mt19937(rd()); +} + +ngtcp2_tstamp timestamp(struct ev_loop *loop) { + return std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} + +bool numeric_host(const char *hostname) { + return numeric_host(hostname, AF_INET) || numeric_host(hostname, AF_INET6); +} + +bool numeric_host(const char *hostname, int family) { + int rv; + std::array<uint8_t, sizeof(struct in6_addr)> dst; + + rv = inet_pton(family, hostname, dst.data()); + + return rv == 1; +} + +namespace { +void hexdump8(FILE *out, const uint8_t *first, const uint8_t *last) { + auto stop = std::min(first + 8, last); + for (auto k = first; k != stop; ++k) { + fprintf(out, "%02x ", *k); + } + // each byte needs 3 spaces (2 hex value and space) + for (; stop != first + 8; ++stop) { + fputs(" ", out); + } + // we have extra space after 8 bytes + fputc(' ', out); +} +} // namespace + +void hexdump(FILE *out, const uint8_t *src, size_t len) { + if (len == 0) { + return; + } + size_t buflen = 0; + auto repeated = false; + std::array<uint8_t, 16> buf{}; + auto end = src + len; + auto i = src; + for (;;) { + auto nextlen = + std::min(static_cast<size_t>(16), static_cast<size_t>(end - i)); + if (nextlen == buflen && + std::equal(std::begin(buf), std::begin(buf) + buflen, i)) { + // as long as adjacent 16 bytes block are the same, we just + // print single '*'. + if (!repeated) { + repeated = true; + fputs("*\n", out); + } + i += nextlen; + continue; + } + repeated = false; + fprintf(out, "%08lx", static_cast<unsigned long>(i - src)); + if (i == end) { + fputc('\n', out); + break; + } + fputs(" ", out); + hexdump8(out, i, end); + hexdump8(out, i + 8, std::max(i + 8, end)); + fputc('|', out); + auto stop = std::min(i + 16, end); + buflen = stop - i; + auto p = buf.data(); + for (; i != stop; ++i) { + *p++ = *i; + if (0x20 <= *i && *i <= 0x7e) { + fputc(*i, out); + } else { + fputc('.', out); + } + } + fputs("|\n", out); + } +} + +std::string make_cid_key(const ngtcp2_cid *cid) { + return std::string(cid->data, cid->data + cid->datalen); +} + +std::string make_cid_key(const uint8_t *cid, size_t cidlen) { + return std::string(cid, cid + cidlen); +} + +std::string straddr(const sockaddr *sa, socklen_t salen) { + std::array<char, NI_MAXHOST> host; + std::array<char, NI_MAXSERV> port; + + auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), + port.size(), NI_NUMERICHOST | NI_NUMERICSERV); + if (rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return ""; + } + std::string res = "["; + res.append(host.data(), strlen(host.data())); + res += "]:"; + res.append(port.data(), strlen(port.data())); + return res; +} + +std::string_view strccalgo(ngtcp2_cc_algo cc_algo) { + switch (cc_algo) { + case NGTCP2_CC_ALGO_RENO: + return "reno"sv; + case NGTCP2_CC_ALGO_CUBIC: + return "cubic"sv; + case NGTCP2_CC_ALGO_BBR: + return "bbr"sv; + case NGTCP2_CC_ALGO_BBR2: + return "bbr2"sv; + default: + assert(0); + abort(); + } +} + +namespace { +constexpr bool rws(char c) { return c == '\t' || c == ' '; } +} // namespace + +std::optional<std::unordered_map<std::string, std::string>> +read_mime_types(const std::string_view &filename) { + std::ifstream f(filename.data()); + if (!f) { + return {}; + } + + std::unordered_map<std::string, std::string> dest; + + std::string line; + while (std::getline(f, line)) { + if (line.empty() || line[0] == '#') { + continue; + } + + auto p = std::find_if(std::begin(line), std::end(line), rws); + if (p == std::begin(line) || p == std::end(line)) { + continue; + } + + auto media_type = std::string{std::begin(line), p}; + for (;;) { + auto ext = std::find_if_not(p, std::end(line), rws); + if (ext == std::end(line)) { + break; + } + + p = std::find_if(ext, std::end(line), rws); + dest.emplace(std::string{ext, p}, media_type); + } + } + + return dest; +} + +std::string format_duration(ngtcp2_duration n) { + if (n >= 3600 * NGTCP2_SECONDS && (n % (3600 * NGTCP2_SECONDS)) == 0) { + return format_uint(n / (3600 * NGTCP2_SECONDS)) + 'h'; + } + if (n >= 60 * NGTCP2_SECONDS && (n % (60 * NGTCP2_SECONDS)) == 0) { + return format_uint(n / (60 * NGTCP2_SECONDS)) + 'm'; + } + if (n >= NGTCP2_SECONDS && (n % NGTCP2_SECONDS) == 0) { + return format_uint(n / NGTCP2_SECONDS) + 's'; + } + if (n >= NGTCP2_MILLISECONDS && (n % NGTCP2_MILLISECONDS) == 0) { + return format_uint(n / NGTCP2_MILLISECONDS) + "ms"; + } + if (n >= NGTCP2_MICROSECONDS && (n % NGTCP2_MICROSECONDS) == 0) { + return format_uint(n / NGTCP2_MICROSECONDS) + "us"; + } + return format_uint(n) + "ns"; +} + +namespace { +std::optional<std::pair<uint64_t, size_t>> +parse_uint_internal(const std::string_view &s) { + uint64_t res = 0; + + if (s.empty()) { + return {}; + } + + for (size_t i = 0; i < s.size(); ++i) { + auto c = s[i]; + if (c < '0' || '9' < c) { + return {{res, i}}; + } + + auto d = c - '0'; + if (res > (std::numeric_limits<uint64_t>::max() - d) / 10) { + return {}; + } + + res *= 10; + res += d; + } + + return {{res, s.size()}}; +} +} // namespace + +std::optional<uint64_t> parse_uint(const std::string_view &s) { + auto o = parse_uint_internal(s); + if (!o) { + return {}; + } + auto [res, idx] = *o; + if (idx != s.size()) { + return {}; + } + return res; +} + +std::optional<uint64_t> parse_uint_iec(const std::string_view &s) { + auto o = parse_uint_internal(s); + if (!o) { + return {}; + } + auto [res, idx] = *o; + if (idx == s.size()) { + return res; + } + if (idx + 1 != s.size()) { + return {}; + } + + uint64_t m; + switch (s[idx]) { + case 'G': + case 'g': + m = 1 << 30; + break; + case 'M': + case 'm': + m = 1 << 20; + break; + case 'K': + case 'k': + m = 1 << 10; + break; + default: + return {}; + } + + if (res > std::numeric_limits<uint64_t>::max() / m) { + return {}; + } + + return res * m; +} + +std::optional<uint64_t> parse_duration(const std::string_view &s) { + auto o = parse_uint_internal(s); + if (!o) { + return {}; + } + auto [res, idx] = *o; + if (idx == s.size()) { + return res * NGTCP2_SECONDS; + } + + uint64_t m; + if (idx + 1 == s.size()) { + switch (s[idx]) { + case 'H': + case 'h': + m = 3600 * NGTCP2_SECONDS; + break; + case 'M': + case 'm': + m = 60 * NGTCP2_SECONDS; + break; + case 'S': + case 's': + m = NGTCP2_SECONDS; + break; + default: + return {}; + } + } else if (idx + 2 == s.size() && (s[idx + 1] == 's' || s[idx + 1] == 'S')) { + switch (s[idx]) { + case 'M': + case 'm': + m = NGTCP2_MILLISECONDS; + break; + case 'U': + case 'u': + m = NGTCP2_MICROSECONDS; + break; + case 'N': + case 'n': + return res; + default: + return {}; + } + } else { + return {}; + } + + if (res > std::numeric_limits<uint64_t>::max() / m) { + return {}; + } + + return res * m; +} + +namespace { +template <typename InputIt> InputIt eat_file(InputIt first, InputIt last) { + if (first == last) { + *first++ = '/'; + return first; + } + + if (*(last - 1) == '/') { + return last; + } + + auto p = last; + for (; p != first && *(p - 1) != '/'; --p) + ; + if (p == first) { + // this should not happened in normal case, where we expect path + // starts with '/' + *first++ = '/'; + return first; + } + + return p; +} +} // namespace + +namespace { +template <typename InputIt> InputIt eat_dir(InputIt first, InputIt last) { + auto p = eat_file(first, last); + + --p; + + assert(*p == '/'); + + return eat_file(first, p); +} +} // namespace + +std::string normalize_path(const std::string_view &path) { + assert(path.size() <= 1024); + assert(path.size() > 0); + assert(path[0] == '/'); + + std::array<char, 1024> res; + auto p = res.data(); + + auto first = std::begin(path); + auto last = std::end(path); + + *p++ = '/'; + ++first; + for (; first != last && *first == '/'; ++first) + ; + + for (; first != last;) { + if (*first == '.') { + if (first + 1 == last) { + break; + } + if (*(first + 1) == '/') { + first += 2; + continue; + } + if (*(first + 1) == '.') { + if (first + 2 == last) { + p = eat_dir(res.data(), p); + break; + } + if (*(first + 2) == '/') { + p = eat_dir(res.data(), p); + first += 3; + continue; + } + } + } + if (*(p - 1) != '/') { + p = eat_file(res.data(), p); + } + auto slash = std::find(first, last, '/'); + if (slash == last) { + p = std::copy(first, last, p); + break; + } + p = std::copy(first, slash + 1, p); + first = slash + 1; + for (; first != last && *first == '/'; ++first) + ; + } + return std::string{res.data(), p}; +} + +int make_socket_nonblocking(int fd) { + int rv; + int flags; + + while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR) + ; + if (flags == -1) { + return -1; + } + + while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR) + ; + + return rv; +} + +int create_nonblock_socket(int domain, int type, int protocol) { +#ifdef SOCK_NONBLOCK + auto fd = socket(domain, type | SOCK_NONBLOCK, protocol); + if (fd == -1) { + return -1; + } +#else // !SOCK_NONBLOCK + auto fd = socket(domain, type, protocol); + if (fd == -1) { + return -1; + } + + make_socket_nonblocking(fd); +#endif // !SOCK_NONBLOCK + + return fd; +} + +std::vector<std::string_view> split_str(const std::string_view &s, char delim) { + size_t len = 1; + auto last = std::end(s); + std::string_view::const_iterator d; + for (auto first = std::begin(s); (d = std::find(first, last, delim)) != last; + ++len, first = d + 1) + ; + + auto list = std::vector<std::string_view>(len); + + len = 0; + for (auto first = std::begin(s);; ++len) { + auto stop = std::find(first, last, delim); + // xcode clang does not understand std::string_view{first, stop}. + list[len] = std::string_view{first, static_cast<size_t>(stop - first)}; + if (stop == last) { + break; + } + first = stop + 1; + } + return list; +} + +std::optional<uint32_t> parse_version(const std::string_view &s) { + auto k = s; + if (!util::istarts_with(k, "0x"sv)) { + return {}; + } + k = k.substr(2); + uint32_t v; + auto rv = std::from_chars(k.data(), k.data() + k.size(), v, 16); + if (rv.ptr != k.data() + k.size() || rv.ec != std::errc{}) { + return {}; + } + + return v; +} + +} // namespace util + +std::ostream &operator<<(std::ostream &os, const ngtcp2_cid &cid) { + return os << "0x" << util::format_hex(cid.data, cid.datalen); +} + +} // namespace ngtcp2 diff --git a/examples/util.h b/examples/util.h new file mode 100644 index 0000000..c83449d --- /dev/null +++ b/examples/util.h @@ -0,0 +1,361 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef UTIL_H +#define UTIL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif // HAVE_CONFIG_H + +#include <sys/socket.h> + +#include <optional> +#include <string> +#include <random> +#include <unordered_map> +#include <string_view> + +#include <ngtcp2/ngtcp2.h> +#include <nghttp3/nghttp3.h> + +#include <ev.h> + +namespace ngtcp2 { + +namespace util { + +inline nghttp3_nv make_nv(const std::string_view &name, + const std::string_view &value, uint8_t flags) { + return nghttp3_nv{ + reinterpret_cast<uint8_t *>(const_cast<char *>(std::data(name))), + reinterpret_cast<uint8_t *>(const_cast<char *>(std::data(value))), + name.size(), + value.size(), + flags, + }; +} + +inline nghttp3_nv make_nv_cc(const std::string_view &name, + const std::string_view &value) { + return make_nv(name, value, NGHTTP3_NV_FLAG_NONE); +} + +inline nghttp3_nv make_nv_nc(const std::string_view &name, + const std::string_view &value) { + return make_nv(name, value, NGHTTP3_NV_FLAG_NO_COPY_NAME); +} + +inline nghttp3_nv make_nv_nn(const std::string_view &name, + const std::string_view &value) { + return make_nv(name, value, + NGHTTP3_NV_FLAG_NO_COPY_NAME | NGHTTP3_NV_FLAG_NO_COPY_VALUE); +} + +std::string format_hex(uint8_t c); + +std::string format_hex(const uint8_t *s, size_t len); + +std::string format_hex(const std::string_view &s); + +template <size_t N> std::string format_hex(const uint8_t (&s)[N]) { + return format_hex(s, N); +} + +std::string decode_hex(const std::string_view &s); + +// format_durationf formats |ns| in human readable manner. |ns| must +// be nanoseconds resolution. This function uses the largest unit so +// that the integral part is strictly more than zero, and the +// precision is at most 2 digits. For example, 1234 is formatted as +// "1.23us". The largest unit is seconds. +std::string format_durationf(uint64_t ns); + +std::mt19937 make_mt19937(); + +ngtcp2_tstamp timestamp(struct ev_loop *loop); + +bool numeric_host(const char *hostname); + +bool numeric_host(const char *hostname, int family); + +// Dumps |src| of length |len| in the format similar to `hexdump -C`. +void hexdump(FILE *out, const uint8_t *src, size_t len); + +inline char lowcase(char c) { + constexpr static unsigned char tbl[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', + 'z', 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, + 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255, + }; + return tbl[static_cast<unsigned char>(c)]; +} + +struct CaseCmp { + bool operator()(char lhs, char rhs) const { + return lowcase(lhs) == lowcase(rhs); + } +}; + +template <typename InputIterator1, typename InputIterator2> +bool istarts_with(InputIterator1 first1, InputIterator1 last1, + InputIterator2 first2, InputIterator2 last2) { + if (last1 - first1 < last2 - first2) { + return false; + } + return std::equal(first2, last2, first1, CaseCmp()); +} + +template <typename S, typename T> bool istarts_with(const S &a, const T &b) { + return istarts_with(a.begin(), a.end(), b.begin(), b.end()); +} + +// make_cid_key returns the key for |cid|. +std::string make_cid_key(const ngtcp2_cid *cid); +std::string make_cid_key(const uint8_t *cid, size_t cidlen); + +// straddr stringifies |sa| of length |salen| in a format "[IP]:PORT". +std::string straddr(const sockaddr *sa, socklen_t salen); + +// strccalgo stringifies |cc_algo|. +std::string_view strccalgo(ngtcp2_cc_algo cc_algo); + +template <typename T, size_t N> +bool streq_l(const T (&a)[N], const nghttp3_vec &b) { + return N - 1 == b.len && memcmp(a, b.base, N - 1) == 0; +} + +namespace { +constexpr char B64_CHARS[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', +}; +} // namespace + +template <typename InputIt> std::string b64encode(InputIt first, InputIt last) { + std::string res; + size_t len = last - first; + if (len == 0) { + return res; + } + size_t r = len % 3; + res.resize((len + 2) / 3 * 4); + auto j = last - r; + auto p = std::begin(res); + while (first != j) { + uint32_t n = static_cast<uint8_t>(*first++) << 16; + n += static_cast<uint8_t>(*first++) << 8; + n += static_cast<uint8_t>(*first++); + *p++ = B64_CHARS[n >> 18]; + *p++ = B64_CHARS[(n >> 12) & 0x3fu]; + *p++ = B64_CHARS[(n >> 6) & 0x3fu]; + *p++ = B64_CHARS[n & 0x3fu]; + } + + if (r == 2) { + uint32_t n = static_cast<uint8_t>(*first++) << 16; + n += static_cast<uint8_t>(*first++) << 8; + *p++ = B64_CHARS[n >> 18]; + *p++ = B64_CHARS[(n >> 12) & 0x3fu]; + *p++ = B64_CHARS[(n >> 6) & 0x3fu]; + *p++ = '='; + } else if (r == 1) { + uint32_t n = static_cast<uint8_t>(*first++) << 16; + *p++ = B64_CHARS[n >> 18]; + *p++ = B64_CHARS[(n >> 12) & 0x3fu]; + *p++ = '='; + *p++ = '='; + } + return res; +} + +// read_mime_types reads "MIME media types and the extensions" file +// denoted by |filename| and returns the mapping of extension to MIME +// media type. +std::optional<std::unordered_map<std::string, std::string>> +read_mime_types(const std::string_view &filename); + +// format_uint converts |n| into string. +template <typename T> std::string format_uint(T n) { + std::string res; + if (n == 0) { + res = "0"; + return res; + } + size_t nlen = 0; + for (auto t = n; t; t /= 10, ++nlen) + ; + res.resize(nlen); + for (; n; n /= 10) { + res[--nlen] = (n % 10) + '0'; + } + return res; +} + +// format_uint_iec converts |n| into string with the IEC unit (either +// "G", "M", or "K"). It chooses the largest unit which does not drop +// precision. +template <typename T> std::string format_uint_iec(T n) { + if (n >= (1 << 30) && (n & ((1 << 30) - 1)) == 0) { + return format_uint(n / (1 << 30)) + 'G'; + } + if (n >= (1 << 20) && (n & ((1 << 20) - 1)) == 0) { + return format_uint(n / (1 << 20)) + 'M'; + } + if (n >= (1 << 10) && (n & ((1 << 10) - 1)) == 0) { + return format_uint(n / (1 << 10)) + 'K'; + } + return format_uint(n); +} + +// format_duration converts |n| into string with the unit in either +// "h" (hours), "m" (minutes), "s" (seconds), "ms" (milliseconds), +// "us" (microseconds) or "ns" (nanoseconds). It chooses the largest +// unit which does not drop precision. |n| is in nanosecond +// resolution. +std::string format_duration(ngtcp2_duration n); + +// parse_uint parses |s| as 64-bit unsigned integer. If it cannot +// parse |s|, the return value does not contain a value. +std::optional<uint64_t> parse_uint(const std::string_view &s); + +// parse_uint_iec parses |s| as 64-bit unsigned integer. It accepts +// IEC unit letter (either "G", "M", or "K") in |s|. If it cannot +// parse |s|, the return value does not contain a value. +std::optional<uint64_t> parse_uint_iec(const std::string_view &s); + +// parse_duration parses |s| as 64-bit unsigned integer. It accepts a +// unit (either "h", "m", "s", "ms", "us", or "ns") in |s|. If no +// unit is present, the unit "s" is assumed. If it cannot parse |s|, +// the return value does not contain a value. +std::optional<uint64_t> parse_duration(const std::string_view &s); + +// generate_secure_random generates a cryptographically secure pseudo +// random data of |datalen| bytes and stores to the buffer pointed by +// |data|. +int generate_secure_random(uint8_t *data, size_t datalen); + +// generate_secret generates secret and writes it to the buffer +// pointed by |secret| of length |secretlen|. Currently, |secretlen| +// must be 32. +int generate_secret(uint8_t *secret, size_t secretlen); + +// normalize_path removes ".." by consuming a previous path component. +// It also removes ".". It assumes that |path| starts with "/". If +// it cannot consume a previous path component, it just removes "..". +std::string normalize_path(const std::string_view &path); + +constexpr bool is_digit(const char c) { return '0' <= c && c <= '9'; } + +constexpr bool is_hex_digit(const char c) { + return is_digit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f'); +} + +// Returns integer corresponding to hex notation |c|. If +// is_hex_digit(c) is false, it returns 256. +constexpr uint32_t hex_to_uint(char c) { + if (c <= '9') { + return c - '0'; + } + if (c <= 'Z') { + return c - 'A' + 10; + } + if (c <= 'z') { + return c - 'a' + 10; + } + return 256; +} + +template <typename InputIt> +std::string percent_decode(InputIt first, InputIt last) { + std::string result; + result.resize(last - first); + auto p = std::begin(result); + for (; first != last; ++first) { + if (*first != '%') { + *p++ = *first; + continue; + } + + if (first + 1 != last && first + 2 != last && is_hex_digit(*(first + 1)) && + is_hex_digit(*(first + 2))) { + *p++ = (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2)); + first += 2; + continue; + } + + *p++ = *first; + } + result.resize(p - std::begin(result)); + return result; +} + +int make_socket_nonblocking(int fd); + +int create_nonblock_socket(int domain, int type, int protocol); + +std::optional<std::string> read_token(const std::string_view &filename); +int write_token(const std::string_view &filename, const uint8_t *token, + size_t tokenlen); + +const char *crypto_default_ciphers(); + +const char *crypto_default_groups(); + +// split_str parses delimited strings in |s| and returns substrings +// delimited by |delim|. The any white spaces around substring are +// treated as a part of substring. +std::vector<std::string_view> split_str(const std::string_view &s, + char delim = ','); + +// parse_version parses |s| to get 4 byte QUIC version. |s| must be a +// hex string and must start with "0x" (.e.g, 0x00000001). +std::optional<uint32_t> parse_version(const std::string_view &s); + +} // namespace util + +std::ostream &operator<<(std::ostream &os, const ngtcp2_cid &cid); + +} // namespace ngtcp2 + +#endif // UTIL_H diff --git a/examples/util_gnutls.cc b/examples/util_gnutls.cc new file mode 100644 index 0000000..a33ca6e --- /dev/null +++ b/examples/util_gnutls.cc @@ -0,0 +1,136 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util.h" + +#include <cassert> +#include <iostream> +#include <fstream> +#include <array> + +#include <ngtcp2/ngtcp2_crypto.h> + +#include <gnutls/crypto.h> + +#include "template.h" + +// Based on https://github.com/ueno/ngtcp2-gnutls-examples + +namespace ngtcp2 { + +namespace util { + +int generate_secure_random(uint8_t *data, size_t datalen) { + if (gnutls_rnd(GNUTLS_RND_RANDOM, data, datalen) != 0) { + return -1; + } + + return 0; +} + +int generate_secret(uint8_t *secret, size_t secretlen) { + std::array<uint8_t, 16> rand; + std::array<uint8_t, 32> md; + + assert(md.size() == secretlen); + + if (generate_secure_random(rand.data(), rand.size()) != 0) { + return -1; + } + + if (gnutls_hash_fast(GNUTLS_DIG_SHA256, rand.data(), rand.size(), + md.data()) != 0) { + return -1; + } + + std::copy_n(std::begin(md), secretlen, secret); + return 0; +} + +std::optional<std::string> read_token(const std::string_view &filename) { + auto f = std::ifstream(filename.data()); + if (!f) { + std::cerr << "Could not read token file " << filename << std::endl; + return {}; + } + + auto pos = f.tellg(); + std::vector<char> content(pos); + f.seekg(0, std::ios::beg); + f.read(content.data(), pos); + + gnutls_datum_t s; + s.data = reinterpret_cast<unsigned char *>(content.data()); + s.size = content.size(); + + gnutls_datum_t d; + if (auto rv = gnutls_pem_base64_decode2("QUIC TOKEN", &s, &d); rv < 0) { + std::cerr << "Could not read token in " << filename << std::endl; + return {}; + } + + auto res = std::string{d.data, d.data + d.size}; + + gnutls_free(d.data); + + return res; +} + +int write_token(const std::string_view &filename, const uint8_t *token, + size_t tokenlen) { + auto f = std::ofstream(filename.data()); + if (!f) { + std::cerr << "Could not write token in " << filename << std::endl; + return -1; + } + + gnutls_datum_t s; + s.data = const_cast<uint8_t *>(token); + s.size = tokenlen; + + gnutls_datum_t d; + if (auto rv = gnutls_pem_base64_encode2("QUIC TOKEN", &s, &d); rv < 0) { + std::cerr << "Could not encode token in " << filename << std::endl; + return -1; + } + + f.write(reinterpret_cast<const char *>(d.data), d.size); + gnutls_free(d.data); + + return 0; +} + +const char *crypto_default_ciphers() { + return "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" + "+CHACHA20-POLY1305:+AES-128-CCM"; +} + +const char *crypto_default_groups() { + return "-GROUP-ALL:+GROUP-X25519:+GROUP-SECP256R1:+GROUP-SECP384R1:" + "+GROUP-SECP521R1"; +} + +} // namespace util + +} // namespace ngtcp2 diff --git a/examples/util_openssl.cc b/examples/util_openssl.cc new file mode 100644 index 0000000..116505b --- /dev/null +++ b/examples/util_openssl.cc @@ -0,0 +1,131 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util.h" + +#include <cassert> +#include <iostream> +#include <array> + +#include <ngtcp2/ngtcp2_crypto.h> + +#include <openssl/bio.h> +#include <openssl/ssl.h> +#include <openssl/evp.h> +#include <openssl/rand.h> + +#include "template.h" + +namespace ngtcp2 { + +namespace util { + +int generate_secure_random(uint8_t *data, size_t datalen) { + if (RAND_bytes(data, static_cast<int>(datalen)) != 1) { + return -1; + } + + return 0; +} + +int generate_secret(uint8_t *secret, size_t secretlen) { + std::array<uint8_t, 16> rand; + std::array<uint8_t, 32> md; + + assert(md.size() == secretlen); + + if (generate_secure_random(rand.data(), rand.size()) != 0) { + return -1; + } + + auto ctx = EVP_MD_CTX_new(); + if (ctx == nullptr) { + return -1; + } + + auto ctx_deleter = defer(EVP_MD_CTX_free, ctx); + + unsigned int mdlen = md.size(); + if (!EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) || + !EVP_DigestUpdate(ctx, rand.data(), rand.size()) || + !EVP_DigestFinal_ex(ctx, md.data(), &mdlen)) { + return -1; + } + + std::copy_n(std::begin(md), secretlen, secret); + return 0; +} + +std::optional<std::string> read_token(const std::string_view &filename) { + auto f = BIO_new_file(filename.data(), "r"); + if (f == nullptr) { + std::cerr << "Could not open token file " << filename << std::endl; + return {}; + } + + auto f_d = defer(BIO_free, f); + + char *name, *header; + unsigned char *data; + long datalen; + std::string token; + if (PEM_read_bio(f, &name, &header, &data, &datalen) != 1) { + std::cerr << "Could not read token file " << filename << std::endl; + return {}; + } + + OPENSSL_free(name); + OPENSSL_free(header); + + auto res = std::string{data, data + datalen}; + + OPENSSL_free(data); + + return res; +} + +int write_token(const std::string_view &filename, const uint8_t *token, + size_t tokenlen) { + auto f = BIO_new_file(filename.data(), "w"); + if (f == nullptr) { + std::cerr << "Could not write token in " << filename << std::endl; + return -1; + } + + PEM_write_bio(f, "QUIC TOKEN", "", token, tokenlen); + BIO_free(f); + + return 0; +} + +const char *crypto_default_ciphers() { + return "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_" + "SHA256:TLS_AES_128_CCM_SHA256"; +} + +const char *crypto_default_groups() { return "X25519:P-256:P-384:P-521"; } + +} // namespace util + +} // namespace ngtcp2 diff --git a/examples/util_test.cc b/examples/util_test.cc new file mode 100644 index 0000000..d59aab2 --- /dev/null +++ b/examples/util_test.cc @@ -0,0 +1,237 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util_test.h" + +#include <limits> + +#include <CUnit/CUnit.h> + +#include "util.h" + +namespace ngtcp2 { + +void test_util_format_durationf() { + CU_ASSERT("0ns" == util::format_durationf(0)); + CU_ASSERT("999ns" == util::format_durationf(999)); + CU_ASSERT("1.00us" == util::format_durationf(1000)); + CU_ASSERT("1.00us" == util::format_durationf(1004)); + CU_ASSERT("1.00us" == util::format_durationf(1005)); + CU_ASSERT("1.02us" == util::format_durationf(1015)); + CU_ASSERT("2.00us" == util::format_durationf(1999)); + CU_ASSERT("1.00ms" == util::format_durationf(999999)); + CU_ASSERT("3.50ms" == util::format_durationf(3500111)); + CU_ASSERT("9999.99s" == util::format_durationf(9999990000000llu)); +} + +void test_util_format_uint() { + CU_ASSERT("0" == util::format_uint(0)); + CU_ASSERT("18446744073709551615" == + util::format_uint(18446744073709551615ull)); +} + +void test_util_format_uint_iec() { + CU_ASSERT("0" == util::format_uint_iec(0)); + CU_ASSERT("1023" == util::format_uint_iec((1 << 10) - 1)); + CU_ASSERT("1K" == util::format_uint_iec(1 << 10)); + CU_ASSERT("1M" == util::format_uint_iec(1 << 20)); + CU_ASSERT("1G" == util::format_uint_iec(1 << 30)); + CU_ASSERT("18446744073709551615" == + util::format_uint_iec(std::numeric_limits<uint64_t>::max())); + CU_ASSERT("1025K" == util::format_uint_iec((1 << 20) + (1 << 10))); +} + +void test_util_format_duration() { + CU_ASSERT("0ns" == util::format_duration(0)); + CU_ASSERT("999ns" == util::format_duration(999)); + CU_ASSERT("1us" == util::format_duration(1000)); + CU_ASSERT("1ms" == util::format_duration(1000000)); + CU_ASSERT("1s" == util::format_duration(1000000000)); + CU_ASSERT("1m" == util::format_duration(60000000000ull)); + CU_ASSERT("1h" == util::format_duration(3600000000000ull)); + CU_ASSERT("18446744073709551615ns" == + util::format_duration(std::numeric_limits<uint64_t>::max())); + CU_ASSERT("61s" == util::format_duration(61000000000ull)); +} + +void test_util_parse_uint() { + { + auto res = util::parse_uint("0"); + CU_ASSERT(res.has_value()); + CU_ASSERT(0 == *res); + } + { + auto res = util::parse_uint("1"); + CU_ASSERT(res.has_value()); + CU_ASSERT(1 == *res); + } + { + auto res = util::parse_uint("18446744073709551615"); + CU_ASSERT(res.has_value()); + CU_ASSERT(18446744073709551615ull == *res); + } + { + auto res = util::parse_uint("18446744073709551616"); + CU_ASSERT(!res.has_value()); + } + { + auto res = util::parse_uint("a"); + CU_ASSERT(!res.has_value()); + } + { + auto res = util::parse_uint("1a"); + CU_ASSERT(!res.has_value()); + } +} + +void test_util_parse_uint_iec() { + { + auto res = util::parse_uint_iec("0"); + CU_ASSERT(res.has_value()); + CU_ASSERT(0 == *res); + } + { + auto res = util::parse_uint_iec("1023"); + CU_ASSERT(res.has_value()); + CU_ASSERT(1023 == *res); + } + { + auto res = util::parse_uint_iec("1K"); + CU_ASSERT(res.has_value()); + CU_ASSERT(1 << 10 == *res); + } + { + auto res = util::parse_uint_iec("1M"); + CU_ASSERT(res.has_value()); + CU_ASSERT(1 << 20 == *res); + } + { + auto res = util::parse_uint_iec("1G"); + CU_ASSERT(res.has_value()); + CU_ASSERT(1 << 30 == *res); + } + { + auto res = util::parse_uint_iec("11G"); + CU_ASSERT(res.has_value()); + CU_ASSERT((1ull << 30) * 11 == *res); + } + { + auto res = util::parse_uint_iec("18446744073709551616"); + CU_ASSERT(!res.has_value()); + } + { + auto res = util::parse_uint_iec("1x"); + CU_ASSERT(!res.has_value()); + } + { + auto res = util::parse_uint_iec("1Gx"); + CU_ASSERT(!res.has_value()); + } +} + +void test_util_parse_duration() { + { + auto res = util::parse_duration("0"); + CU_ASSERT(res.has_value()); + CU_ASSERT(0 == *res); + } + { + auto res = util::parse_duration("1"); + CU_ASSERT(res.has_value()); + CU_ASSERT(NGTCP2_SECONDS == *res); + } + { + auto res = util::parse_duration("0ns"); + CU_ASSERT(res.has_value()); + CU_ASSERT(0 == *res); + } + { + auto res = util::parse_duration("1ns"); + CU_ASSERT(res.has_value()); + CU_ASSERT(1 == *res); + } + { + auto res = util::parse_duration("1us"); + CU_ASSERT(res.has_value()); + CU_ASSERT(NGTCP2_MICROSECONDS == *res); + } + { + auto res = util::parse_duration("1ms"); + CU_ASSERT(res.has_value()); + CU_ASSERT(NGTCP2_MILLISECONDS == *res); + } + { + auto res = util::parse_duration("1s"); + CU_ASSERT(res.has_value()); + CU_ASSERT(NGTCP2_SECONDS == *res); + } + { + auto res = util::parse_duration("1m"); + CU_ASSERT(res.has_value()); + CU_ASSERT(60 * NGTCP2_SECONDS == *res); + } + { + auto res = util::parse_duration("1h"); + CU_ASSERT(res.has_value()); + CU_ASSERT(3600 * NGTCP2_SECONDS == *res); + } + { + auto res = util::parse_duration("2h"); + CU_ASSERT(res.has_value()); + CU_ASSERT(2 * 3600 * NGTCP2_SECONDS == *res); + } + { + auto res = util::parse_duration("18446744073709551616"); + CU_ASSERT(!res.has_value()); + } + { + auto res = util::parse_duration("1x"); + CU_ASSERT(!res.has_value()); + } + { + auto res = util::parse_duration("1mx"); + CU_ASSERT(!res.has_value()); + } + { + auto res = util::parse_duration("1mxy"); + CU_ASSERT(!res.has_value()); + } +} + +void test_util_normalize_path() { + CU_ASSERT("/" == util::normalize_path("/")); + CU_ASSERT("/" == util::normalize_path("//")); + CU_ASSERT("/foo" == util::normalize_path("/foo")); + CU_ASSERT("/foo/bar/" == util::normalize_path("/foo/bar/")); + CU_ASSERT("/foo/bar/" == util::normalize_path("/foo/abc/../bar/")); + CU_ASSERT("/foo/bar/" == util::normalize_path("/../foo/abc/../bar/")); + CU_ASSERT("/foo/bar/" == + util::normalize_path("/./foo/././abc///.././bar/./")); + CU_ASSERT("/foo/" == util::normalize_path("/foo/.")); + CU_ASSERT("/foo/bar" == util::normalize_path("/foo/./bar")); + CU_ASSERT("/bar" == util::normalize_path("/foo/./../bar")); + CU_ASSERT("/bar" == util::normalize_path("/../../bar")); +} + +} // namespace ngtcp2 diff --git a/examples/util_test.h b/examples/util_test.h new file mode 100644 index 0000000..376d3a9 --- /dev/null +++ b/examples/util_test.h @@ -0,0 +1,45 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef UTIL_TEST_H +#define UTIL_TEST_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif // HAVE_CONFIG_H + +namespace ngtcp2 { + +void test_util_format_durationf(); +void test_util_format_uint(); +void test_util_format_uint_iec(); +void test_util_format_duration(); +void test_util_parse_uint(); +void test_util_parse_uint_iec(); +void test_util_parse_duration(); +void test_util_normalize_path(); + +} // namespace ngtcp2 + +#endif // UTIL_TEST_H diff --git a/examples/util_wolfssl.cc b/examples/util_wolfssl.cc new file mode 100644 index 0000000..80eb096 --- /dev/null +++ b/examples/util_wolfssl.cc @@ -0,0 +1,130 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "util.h" + +#include <cassert> +#include <iostream> +#include <array> + +#include <ngtcp2/ngtcp2_crypto.h> + +#include <wolfssl/options.h> +#include <wolfssl/ssl.h> +#include <wolfssl/openssl/pem.h> + +#include "template.h" + +namespace ngtcp2 { + +namespace util { + +int generate_secure_random(uint8_t *data, size_t datalen) { + if (wolfSSL_RAND_bytes(data, static_cast<int>(datalen)) != 1) { + return -1; + } + + return 0; +} + +int generate_secret(uint8_t *secret, size_t secretlen) { + std::array<uint8_t, 16> rand; + std::array<uint8_t, 32> md; + + assert(md.size() == secretlen); + + if (generate_secure_random(rand.data(), rand.size()) != 0) { + return -1; + } + + auto ctx = wolfSSL_EVP_MD_CTX_new(); + if (ctx == nullptr) { + return -1; + } + + unsigned int mdlen = md.size(); + if (!wolfSSL_EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) || + !wolfSSL_EVP_DigestUpdate(ctx, rand.data(), rand.size()) || + !wolfSSL_EVP_DigestFinal_ex(ctx, md.data(), &mdlen)) { + wolfSSL_EVP_MD_CTX_free(ctx); + return -1; + } + + std::copy_n(std::begin(md), secretlen, secret); + wolfSSL_EVP_MD_CTX_free(ctx); + return 0; +} + +std::optional<std::string> read_token(const std::string_view &filename) { + auto f = wolfSSL_BIO_new_file(filename.data(), "r"); + if (f == nullptr) { + std::cerr << "Could not open token file " << filename << std::endl; + return {}; + } + + char *name, *header; + unsigned char *data; + long datalen; + std::string token; + if (wolfSSL_PEM_read_bio(f, &name, &header, &data, &datalen) != 1) { + std::cerr << "Could not read token file " << filename << std::endl; + wolfSSL_BIO_free(f); + return {}; + } + wolfSSL_BIO_free(f); + + wolfSSL_OPENSSL_free(name); + wolfSSL_OPENSSL_free(header); + + auto res = std::string{data, data + datalen}; + + wolfSSL_OPENSSL_free(data); + + return res; +} + +int write_token(const std::string_view &filename, const uint8_t *token, + size_t tokenlen) { + auto f = wolfSSL_BIO_new_file(filename.data(), "w"); + if (f == nullptr) { + std::cerr << "Could not write token in " << filename << std::endl; + return -1; + } + + wolfSSL_PEM_write_bio(f, "QUIC TOKEN", "", token, tokenlen); + wolfSSL_BIO_free(f); + + return 0; +} + +const char *crypto_default_ciphers() { + return "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_" + "SHA256:TLS_AES_128_CCM_SHA256"; +} + +const char *crypto_default_groups() { return "X25519:P-256:P-384:P-521"; } + +} // namespace util + +} // namespace ngtcp2 diff --git a/fuzz/corpus/decode_frame/ack b/fuzz/corpus/decode_frame/ack Binary files differnew file mode 100644 index 0000000..3460d0d --- /dev/null +++ b/fuzz/corpus/decode_frame/ack diff --git a/fuzz/corpus/decode_frame/ack_ecn b/fuzz/corpus/decode_frame/ack_ecn Binary files differnew file mode 100644 index 0000000..09b2bf7 --- /dev/null +++ b/fuzz/corpus/decode_frame/ack_ecn diff --git a/fuzz/corpus/decode_frame/connection_close b/fuzz/corpus/decode_frame/connection_close Binary files differnew file mode 100644 index 0000000..61409da --- /dev/null +++ b/fuzz/corpus/decode_frame/connection_close diff --git a/fuzz/corpus/decode_frame/crypto b/fuzz/corpus/decode_frame/crypto new file mode 100644 index 0000000..8d03ebf --- /dev/null +++ b/fuzz/corpus/decode_frame/crypto @@ -0,0 +1 @@ +ñòóôõö÷ø0123456789abcdef1
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/data_blocked b/fuzz/corpus/decode_frame/data_blocked new file mode 100644 index 0000000..e195a8c --- /dev/null +++ b/fuzz/corpus/decode_frame/data_blocked @@ -0,0 +1 @@ +ñòóôõö÷ø
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/datagram b/fuzz/corpus/decode_frame/datagram new file mode 100644 index 0000000..72b1e3e --- /dev/null +++ b/fuzz/corpus/decode_frame/datagram @@ -0,0 +1 @@ +00123456789abcdef3
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/datagram_len b/fuzz/corpus/decode_frame/datagram_len new file mode 100644 index 0000000..07198a7 --- /dev/null +++ b/fuzz/corpus/decode_frame/datagram_len @@ -0,0 +1 @@ +10123456789abcdef3
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/max_data b/fuzz/corpus/decode_frame/max_data new file mode 100644 index 0000000..9c0d924 --- /dev/null +++ b/fuzz/corpus/decode_frame/max_data @@ -0,0 +1 @@ +ñòóôõö÷ø
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/max_stream_data b/fuzz/corpus/decode_frame/max_stream_data Binary files differnew file mode 100644 index 0000000..3e45818 --- /dev/null +++ b/fuzz/corpus/decode_frame/max_stream_data diff --git a/fuzz/corpus/decode_frame/max_streams b/fuzz/corpus/decode_frame/max_streams Binary files differnew file mode 100644 index 0000000..17440a6 --- /dev/null +++ b/fuzz/corpus/decode_frame/max_streams diff --git a/fuzz/corpus/decode_frame/new_connection_id b/fuzz/corpus/decode_frame/new_connection_id new file mode 100644 index 0000000..6004466 --- /dev/null +++ b/fuzz/corpus/decode_frame/new_connection_id @@ -0,0 +1 @@ +»šÊ @ÿªªªªªªªªªªªªªªîáááááááááááááááá
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/new_token b/fuzz/corpus/decode_frame/new_token new file mode 100644 index 0000000..8fa359c --- /dev/null +++ b/fuzz/corpus/decode_frame/new_token @@ -0,0 +1 @@ +0123456789abcdef2
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/path_challenge b/fuzz/corpus/decode_frame/path_challenge new file mode 100644 index 0000000..3e94fb7 --- /dev/null +++ b/fuzz/corpus/decode_frame/path_challenge @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/path_response b/fuzz/corpus/decode_frame/path_response new file mode 100644 index 0000000..e33140e --- /dev/null +++ b/fuzz/corpus/decode_frame/path_response @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/reset_stream b/fuzz/corpus/decode_frame/reset_stream Binary files differnew file mode 100644 index 0000000..baee7ce --- /dev/null +++ b/fuzz/corpus/decode_frame/reset_stream diff --git a/fuzz/corpus/decode_frame/retire_connection_id b/fuzz/corpus/decode_frame/retire_connection_id new file mode 100644 index 0000000..15ce00a --- /dev/null +++ b/fuzz/corpus/decode_frame/retire_connection_id @@ -0,0 +1 @@ +»šÊ
\ No newline at end of file diff --git a/fuzz/corpus/decode_frame/stop_sending b/fuzz/corpus/decode_frame/stop_sending Binary files differnew file mode 100644 index 0000000..d9abd24 --- /dev/null +++ b/fuzz/corpus/decode_frame/stop_sending diff --git a/fuzz/corpus/decode_frame/stream b/fuzz/corpus/decode_frame/stream Binary files differnew file mode 100644 index 0000000..f85b928 --- /dev/null +++ b/fuzz/corpus/decode_frame/stream diff --git a/fuzz/corpus/decode_frame/stream_data_blocked b/fuzz/corpus/decode_frame/stream_data_blocked Binary files differnew file mode 100644 index 0000000..8ccc9cc --- /dev/null +++ b/fuzz/corpus/decode_frame/stream_data_blocked diff --git a/fuzz/corpus/decode_frame/stream_len b/fuzz/corpus/decode_frame/stream_len Binary files differnew file mode 100644 index 0000000..c0ad3d6 --- /dev/null +++ b/fuzz/corpus/decode_frame/stream_len diff --git a/fuzz/corpus/decode_frame/streams_blocked b/fuzz/corpus/decode_frame/streams_blocked Binary files differnew file mode 100644 index 0000000..f6fae51 --- /dev/null +++ b/fuzz/corpus/decode_frame/streams_blocked diff --git a/fuzz/corpus/ksl/random b/fuzz/corpus/ksl/random Binary files differnew file mode 100644 index 0000000..b2f626a --- /dev/null +++ b/fuzz/corpus/ksl/random diff --git a/fuzz/decode_frame.cc b/fuzz/decode_frame.cc new file mode 100644 index 0000000..13431fd --- /dev/null +++ b/fuzz/decode_frame.cc @@ -0,0 +1,25 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#include "ngtcp2_conn.h" + +#ifdef __cplusplus +} +#endif + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + for (; size;) { + ngtcp2_max_frame mfr{}; + + auto nread = ngtcp2_pkt_decode_frame(&mfr.fr, data, size); + if (nread < 0) { + return 0; + } + + data += nread; + size -= nread; + } + + return 0; +} diff --git a/fuzz/ksl.cc b/fuzz/ksl.cc new file mode 100644 index 0000000..9bbf4c4 --- /dev/null +++ b/fuzz/ksl.cc @@ -0,0 +1,77 @@ +#include <byteswap.h> + +#include <cstring> +#include <memory> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ngtcp2_ksl.h" + +#ifdef __cplusplus +} +#endif + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + using KeyType = uint16_t; + using DataType = int64_t; + constexpr size_t keylen = sizeof(KeyType); + + auto compar = [](auto *lhs, auto *rhs) -> int { + return *static_cast<const KeyType *>(lhs) < + *static_cast<const KeyType *>(rhs); + }; + + ngtcp2_ksl ksl; + + ngtcp2_ksl_init(&ksl, compar, keylen, ngtcp2_mem_default()); + + for (; size >= keylen; ++data, --size) { + KeyType d; + + memcpy(&d, data, keylen); + + for (size_t i = 0; i < 2; ++i) { + auto add = (d & 0x8000) != 0; + auto key = static_cast<KeyType>(d & 0x7fff); + + if (add) { + auto data = std::make_unique<DataType>(key); + auto rv = ngtcp2_ksl_insert(&ksl, nullptr, &key, data.get()); + if (rv != 0) { + continue; + } + + data.release(); + ngtcp2_ksl_lower_bound(&ksl, &key); + + continue; + } + + auto it = ngtcp2_ksl_lower_bound(&ksl, &key); + if (ngtcp2_ksl_it_end(&it)) { + continue; + } + + if (*static_cast<KeyType *>(ngtcp2_ksl_it_key(&it)) != key) { + continue; + } + + delete static_cast<DataType *>(ngtcp2_ksl_it_get(&it)); + + ngtcp2_ksl_remove(&ksl, nullptr, &key); + + d = bswap_16(d); + } + } + + for (auto it = ngtcp2_ksl_begin(&ksl); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + delete static_cast<DataType *>(ngtcp2_ksl_it_get(&it)); + } + + ngtcp2_ksl_free(&ksl); + + return 0; +} diff --git a/interop/Dockerfile b/interop/Dockerfile new file mode 100644 index 0000000..c5703c0 --- /dev/null +++ b/interop/Dockerfile @@ -0,0 +1,39 @@ +FROM martenseemann/quic-network-simulator-endpoint:latest + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + git gcc clang-12 make binutils autoconf automake autotools-dev libtool \ + pkg-config libev-dev libjemalloc-dev \ + libev4 libjemalloc2 ca-certificates mime-support \ + llvm-12 libasan5 libubsan1 && \ + git clone --depth 1 -b OpenSSL_1_1_1s+quic https://github.com/quictls/openssl && \ + cd openssl && ./config --openssldir=/etc/ssl && make -j$(nproc) && make install_sw && cd .. && rm -rf openssl && \ + git clone --depth 1 https://github.com/ngtcp2/nghttp3 && \ + cd nghttp3 && autoreconf -i && \ + ./configure --enable-lib-only \ + CC=clang-12 \ + CXX=clang++-12 \ + LDFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=undefined" \ + CPPFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=undefined -g3" && \ + make -j$(nproc) && make install && cd .. && rm -rf nghttp3 && \ + git clone --depth 1 https://github.com/ngtcp2/ngtcp2 && \ + cd ngtcp2 && autoreconf -i && \ + ./configure \ + CC=clang-12 \ + CXX=clang++-12 \ + LDFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=undefined" \ + CPPFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=undefined -g3" && \ + make -j$(nproc) && make install && \ + cp examples/server examples/client examples/h09server examples/h09client /usr/local/bin && \ + cd .. && \ + rm -rf ngtcp2 && \ + rm -rf /usr/local/lib/libssl.so /usr/local/lib/libcrypto.so /usr/local/lib/libssl.a /usr/local/lib/libcrypto.a /usr/local/lib/pkgconfig/*ssl.pc /usr/local/include/openssl/* && \ + apt-get -y purge git g++ clang-12 make binutils autoconf automake \ + autotools-dev libtool pkg-config \ + libev-dev libjemalloc-dev && \ + apt-get -y autoremove --purge && \ + rm -rf /var/log/* + +COPY run_endpoint.sh . +RUN chmod +x run_endpoint.sh +ENTRYPOINT [ "./run_endpoint.sh" ] diff --git a/interop/run_endpoint.sh b/interop/run_endpoint.sh new file mode 100644 index 0000000..780c825 --- /dev/null +++ b/interop/run_endpoint.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +# Set up the routing needed for the simulation +/setup.sh + +# The following variables are available for use: +# - ROLE contains the role of this execution context, client or server +# - SERVER_PARAMS contains user-supplied command line parameters +# - CLIENT_PARAMS contains user-supplied command line parameters + +case $TESTCASE in + versionnegotiation|handshake|transfer|retry|resumption|http3|multiconnect|zerortt|chacha20|keyupdate|ecn|v2) + : + ;; + *) + exit 127 + ;; +esac + +LOG=/logs/log.txt + +if [ "$ROLE" == "client" ]; then + # Wait for the simulator to start up. + /wait-for-it.sh sim:57832 -s -t 30 + REQS=($REQUESTS) + SERVER=$(echo ${REQS[0]} | sed -re 's|^https://([^/:]+)(:[0-9]+)?/.*$|\1|') + if [ "$TESTCASE" == "http3" ]; then + CLIENT_BIN="/usr/local/bin/client" + else + CLIENT_BIN="/usr/local/bin/h09client" + fi + CLIENT_ARGS="$SERVER 443 --download /downloads -s --no-quic-dump --no-http-dump --exit-on-all-streams-close --qlog-dir $QLOGDIR --cc bbr2" + if [ "$TESTCASE" == "versionnegotiation" ]; then + CLIENT_ARGS="$CLIENT_ARGS -v 0xaaaaaaaa" + else + CLIENT_ARGS="$CLIENT_ARGS -v 0x1" + fi + if [ "$TESTCASE" == "chacha20" ]; then + CLIENT_ARGS="$CLIENT_ARGS --ciphers=TLS_CHACHA20_POLY1305_SHA256" + fi + if [ "$TESTCASE" == "keyupdate" ]; then + CLIENT_ARGS="$CLIENT_ARGS --delay-stream 10ms --key-update 1ms" + fi + if [ "$TESTCASE" == "v2" ]; then + CLIENT_ARGS="$CLIENT_ARGS --other-versions v2draft,v1" + fi + if [ "$TESTCASE" == "ecn" ]; then + CLIENT_ARGS="$CLIENT_ARGS --no-pmtud" + fi + if [ "$TESTCASE" == "resumption" ] || [ "$TESTCASE" == "zerortt" ]; then + CLIENT_ARGS="$CLIENT_ARGS --session-file session.txt --tp-file tp.txt" + if [ "$TESTCASE" == "resumption" ]; then + CLIENT_ARGS="$CLIENT_ARGS --disable-early-data" + fi + REQUESTS=${REQS[0]} + $CLIENT_BIN $CLIENT_ARGS $REQUESTS $CLIENT_PARAMS &> $LOG + REQUESTS=${REQS[@]:1} + $CLIENT_BIN $CLIENT_ARGS $REQUESTS $CLIENT_PARAMS &>> $LOG + elif [ "$TESTCASE" == "multiconnect" ]; then + CLIENT_ARGS="$CLIENT_ARGS --timeout=180s --handshake-timeout=180s" + for REQ in $REQUESTS; do + echo "multiconnect REQ: $REQ" >> $LOG + $CLIENT_BIN $CLIENT_ARGS $REQ $CLIENT_PARAMS &>> $LOG + done + else + $CLIENT_BIN $CLIENT_ARGS $REQUESTS $CLIENT_PARAMS &> $LOG + fi +elif [ "$ROLE" == "server" ]; then + if [ "$TESTCASE" == "http3" ]; then + SERVER_BIN="/usr/local/bin/server" + else + SERVER_BIN="/usr/local/bin/h09server" + fi + SERVER_ARGS="/certs/priv.key /certs/cert.pem -s -d /www --qlog-dir $QLOGDIR --cc bbr2" + if [ "$TESTCASE" == "retry" ]; then + SERVER_ARGS="$SERVER_ARGS -V" + elif [ "$TESTCASE" == "multiconnect" ]; then + SERVER_ARGS="$SERVER_ARGS --timeout=180s --handshake-timeout=180s" + elif [ "$TESTCASE" == "v2" ]; then + SERVER_ARGS="$SERVER_ARGS --preferred-versions v2draft" + elif [ "$TESTCASE" == "ecn" ]; then + SERVER_ARGS="$SERVER_ARGS --no-pmtud" + fi + + $SERVER_BIN '*' 443 $SERVER_ARGS $SERVER_PARAMS &> $LOG +fi diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..febcd8e --- /dev/null +++ b/lib/.gitignore @@ -0,0 +1,2 @@ +includes/ngtcp2/version.h +libngtcp2.pc diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..c819584 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,109 @@ + +# ngtcp2 + +# Copyright (c) 2016 ngtcp2 + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +add_subdirectory(includes) + +add_definitions(-DBUILDING_NGTCP2) + +set(ngtcp2_SOURCES + ngtcp2_pkt.c + ngtcp2_conv.c + ngtcp2_str.c + ngtcp2_vec.c + ngtcp2_buf.c + ngtcp2_conn.c + ngtcp2_mem.c + ngtcp2_pq.c + ngtcp2_map.c + ngtcp2_rob.c + ngtcp2_ppe.c + ngtcp2_crypto.c + ngtcp2_err.c + ngtcp2_range.c + ngtcp2_acktr.c + ngtcp2_rtb.c + ngtcp2_strm.c + ngtcp2_idtr.c + ngtcp2_gaptr.c + ngtcp2_ringbuf.c + ngtcp2_log.c + ngtcp2_qlog.c + ngtcp2_cid.c + ngtcp2_ksl.c + ngtcp2_cc.c + ngtcp2_bbr.c + ngtcp2_bbr2.c + ngtcp2_addr.c + ngtcp2_path.c + ngtcp2_pv.c + ngtcp2_pmtud.c + ngtcp2_version.c + ngtcp2_rst.c + ngtcp2_window_filter.c + ngtcp2_opl.c + ngtcp2_balloc.c + ngtcp2_objalloc.c + ngtcp2_unreachable.c +) + +set(ngtcp2_INCLUDE_DIRS + "${CMAKE_CURRENT_SOURCE_DIR}/includes" + "${CMAKE_CURRENT_BINARY_DIR}/includes" +) + +# Public shared library +if(ENABLE_SHARED_LIB) + add_library(ngtcp2 SHARED ${ngtcp2_SOURCES}) + set_target_properties(ngtcp2 PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION} + C_VISIBILITY_PRESET hidden + POSITION_INDEPENDENT_CODE ON + ) + target_include_directories(ngtcp2 PUBLIC ${ngtcp2_INCLUDE_DIRS}) + + install(TARGETS ngtcp2 + 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(ngtcp2_static STATIC ${ngtcp2_SOURCES}) + set_target_properties(ngtcp2_static PROPERTIES + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION} + C_VISIBILITY_PRESET hidden + ) + target_compile_definitions(ngtcp2_static PUBLIC "-DNGTCP2_STATICLIB") + target_include_directories(ngtcp2_static PUBLIC ${ngtcp2_INCLUDE_DIRS}) + if(ENABLE_STATIC_LIB) + install(TARGETS ngtcp2_static + DESTINATION "${CMAKE_INSTALL_LIBDIR}") + endif() +endif() + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libngtcp2.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..9180fdf --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,121 @@ +# ngtcp2 + +# Copyright (c) 2016 - 2019 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +SUBDIRS = includes + +EXTRA_DIST = CMakeLists.txt + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) $(EXTRACFLAG) +AM_CPPFLAGS = -I$(srcdir)/includes -I$(builddir)/includes -DBUILDING_NGTCP2 +AM_LDFLAGS = ${LIBTOOL_LDFLAGS} + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libngtcp2.pc +DISTCLEANFILES = $(pkgconfig_DATA) + +lib_LTLIBRARIES = libngtcp2.la + +OBJECTS = \ + ngtcp2_pkt.c \ + ngtcp2_conv.c \ + ngtcp2_str.c \ + ngtcp2_vec.c \ + ngtcp2_buf.c \ + ngtcp2_conn.c \ + ngtcp2_mem.c \ + ngtcp2_pq.c \ + ngtcp2_map.c \ + ngtcp2_rob.c \ + ngtcp2_ppe.c \ + ngtcp2_crypto.c \ + ngtcp2_err.c \ + ngtcp2_range.c \ + ngtcp2_acktr.c \ + ngtcp2_rtb.c \ + ngtcp2_strm.c \ + ngtcp2_idtr.c \ + ngtcp2_gaptr.c \ + ngtcp2_ringbuf.c \ + ngtcp2_log.c \ + ngtcp2_qlog.c \ + ngtcp2_cid.c \ + ngtcp2_ksl.c \ + ngtcp2_cc.c \ + ngtcp2_bbr.c \ + ngtcp2_bbr2.c \ + ngtcp2_addr.c \ + ngtcp2_path.c \ + ngtcp2_pv.c \ + ngtcp2_pmtud.c \ + ngtcp2_version.c \ + ngtcp2_rst.c \ + ngtcp2_window_filter.c \ + ngtcp2_opl.c \ + ngtcp2_balloc.c \ + ngtcp2_objalloc.c \ + ngtcp2_unreachable.c + +HFILES = \ + ngtcp2_pkt.h \ + ngtcp2_conv.h \ + ngtcp2_str.h \ + ngtcp2_vec.h \ + ngtcp2_buf.h \ + ngtcp2_conn.h \ + ngtcp2_mem.h \ + ngtcp2_pq.h \ + ngtcp2_map.h \ + ngtcp2_rob.h \ + ngtcp2_ppe.h \ + ngtcp2_crypto.h \ + ngtcp2_err.h \ + ngtcp2_range.h \ + ngtcp2_acktr.h \ + ngtcp2_rtb.h \ + ngtcp2_strm.h \ + ngtcp2_idtr.h \ + ngtcp2_gaptr.h \ + ngtcp2_ringbuf.h \ + ngtcp2_log.h \ + ngtcp2_qlog.h \ + ngtcp2_cid.h \ + ngtcp2_ksl.h \ + ngtcp2_cc.h \ + ngtcp2_bbr.h \ + ngtcp2_bbr2.h \ + ngtcp2_addr.h \ + ngtcp2_path.h \ + ngtcp2_pv.h \ + ngtcp2_pmtud.h \ + ngtcp2_macro.h \ + ngtcp2_rst.h \ + ngtcp2_window_filter.h \ + ngtcp2_opl.h \ + ngtcp2_balloc.h \ + ngtcp2_objalloc.h \ + ngtcp2_rcvry.h \ + ngtcp2_net.h \ + ngtcp2_unreachable.h + +libngtcp2_la_SOURCES = $(HFILES) $(OBJECTS) +libngtcp2_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..5eabf73 --- /dev/null +++ b/lib/includes/CMakeLists.txt @@ -0,0 +1,4 @@ +install(FILES + ngtcp2/ngtcp2.h + "${CMAKE_CURRENT_BINARY_DIR}/ngtcp2/version.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ngtcp2") diff --git a/lib/includes/Makefile.am b/lib/includes/Makefile.am new file mode 100644 index 0000000..5ecb6a4 --- /dev/null +++ b/lib/includes/Makefile.am @@ -0,0 +1,25 @@ +# ngtcp2 + +# Copyright (c) 2016 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +nobase_include_HEADERS = ngtcp2/ngtcp2.h ngtcp2/version.h diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h new file mode 100644 index 0000000..c3bfa58 --- /dev/null +++ b/lib/includes/ngtcp2/ngtcp2.h @@ -0,0 +1,5893 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2017 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_H +#define NGTCP2_H + +/* Define WIN32 when build target is Win32 API (borrowed from + libcurl) */ +#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) +# define WIN32 +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4324) +#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> + +#ifndef NGTCP2_USE_GENERIC_SOCKADDR +# ifdef WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include <ws2tcpip.h> +# else +# include <sys/socket.h> +# include <netinet/in.h> +# endif +#endif + +#ifdef AF_INET +# define NGTCP2_AF_INET AF_INET +#else +# define NGTCP2_AF_INET 2 +#endif + +#ifdef AF_INET6 +# define NGTCP2_AF_INET6 AF_INET6 +#else +# define NGTCP2_AF_INET6 23 +# define NGTCP2_USE_GENERIC_IPV6_SOCKADDR +#endif + +#include <ngtcp2/version.h> + +#ifdef NGTCP2_STATICLIB +# define NGTCP2_EXTERN +#elif defined(WIN32) +# ifdef BUILDING_NGTCP2 +# define NGTCP2_EXTERN __declspec(dllexport) +# else /* !BUILDING_NGTCP2 */ +# define NGTCP2_EXTERN __declspec(dllimport) +# endif /* !BUILDING_NGTCP2 */ +#else /* !defined(WIN32) */ +# ifdef BUILDING_NGTCP2 +# define NGTCP2_EXTERN __attribute__((visibility("default"))) +# else /* !BUILDING_NGTCP2 */ +# define NGTCP2_EXTERN +# endif /* !BUILDING_NGTCP2 */ +#endif /* !defined(WIN32) */ + +#ifdef _MSC_VER +# define NGTCP2_ALIGN(N) __declspec(align(N)) +#else /* !_MSC_VER */ +# define NGTCP2_ALIGN(N) __attribute__((aligned(N))) +#endif /* !_MSC_VER */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @typedef + * + * :type:`ngtcp2_ssize` is signed counterpart of size_t. + */ +typedef ptrdiff_t ngtcp2_ssize; + +/** + * @functypedef + * + * :type:`ngtcp2_malloc` is a custom memory allocator to replace + * :manpage:`malloc(3)`. The |user_data| is + * :member:`ngtcp2_mem.user_data`. + */ +typedef void *(*ngtcp2_malloc)(size_t size, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_free` is a custom memory allocator to replace + * :manpage:`free(3)`. The |user_data| is + * :member:`ngtcp2_mem.user_data`. + */ +typedef void (*ngtcp2_free)(void *ptr, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_calloc` is a custom memory allocator to replace + * :manpage:`calloc(3)`. The |user_data| is the + * :member:`ngtcp2_mem.user_data`. + */ +typedef void *(*ngtcp2_calloc)(size_t nmemb, size_t size, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_realloc` is a custom memory allocator to replace + * :manpage:`realloc(3)`. The |user_data| is the + * :member:`ngtcp2_mem.user_data`. + */ +typedef void *(*ngtcp2_realloc)(void *ptr, size_t size, void *user_data); + +/** + * @struct + * + * :type:`ngtcp2_mem` is a custom memory allocator. The + * :member:`user_data` field is passed to each allocator function. + * This can be used, for example, to achieve per-connection memory + * pool. + * + * In the following example code, ``my_malloc``, ``my_free``, + * ``my_calloc`` and ``my_realloc`` are the replacement of the + * standard allocators :manpage:`malloc(3)`, :manpage:`free(3)`, + * :manpage:`calloc(3)` and :manpage:`realloc(3)` respectively:: + * + * void *my_malloc_cb(size_t size, void *user_data) { + * (void)user_data; + * return my_malloc(size); + * } + * + * void my_free_cb(void *ptr, void *user_data) { + * (void)user_data; + * my_free(ptr); + * } + * + * void *my_calloc_cb(size_t nmemb, size_t size, void *user_data) { + * (void)user_data; + * return my_calloc(nmemb, size); + * } + * + * void *my_realloc_cb(void *ptr, size_t size, void *user_data) { + * (void)user_data; + * return my_realloc(ptr, size); + * } + * + * void conn_new() { + * ngtcp2_mem mem = {NULL, my_malloc_cb, my_free_cb, my_calloc_cb, + * my_realloc_cb}; + * + * ... + * } + */ +typedef struct ngtcp2_mem { + /** + * :member:`user_data` is an arbitrary user supplied data. This + * is passed to each allocator function. + */ + void *user_data; + /** + * :member:`malloc` is a custom allocator function to replace + * :manpage:`malloc(3)`. + */ + ngtcp2_malloc malloc; + /** + * :member:`free` is a custom allocator function to replace + * :manpage:`free(3)`. + */ + ngtcp2_free free; + /** + * :member:`calloc` is a custom allocator function to replace + * :manpage:`calloc(3)`. + */ + ngtcp2_calloc calloc; + /** + * :member:`realloc` is a custom allocator function to replace + * :manpage:`realloc(3)`. + */ + ngtcp2_realloc realloc; +} ngtcp2_mem; + +/** + * @macrosection + * + * Time related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_SECONDS` is a count of tick which corresponds to 1 second. + */ +#define NGTCP2_SECONDS ((ngtcp2_duration)1000000000ULL) + +/** + * @macro + * + * :macro:`NGTCP2_MILLISECONDS` is a count of tick which corresponds + * to 1 millisecond. + */ +#define NGTCP2_MILLISECONDS ((ngtcp2_duration)1000000ULL) + +/** + * @macro + * + * :macro:`NGTCP2_MICROSECONDS` is a count of tick which corresponds + * to 1 microsecond. + */ +#define NGTCP2_MICROSECONDS ((ngtcp2_duration)1000ULL) + +/** + * @macro + * + * :macro:`NGTCP2_NANOSECONDS` is a count of tick which corresponds to + * 1 nanosecond. + */ +#define NGTCP2_NANOSECONDS ((ngtcp2_duration)1ULL) + +/** + * @macrosection + * + * QUIC protocol version macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_V1` is the QUIC version 1. + */ +#define NGTCP2_PROTO_VER_V1 ((uint32_t)0x00000001u) + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_V2_DRAFT` is the provisional version + * number for QUIC version 2 draft. + * + * https://quicwg.org/quic-v2/draft-ietf-quic-v2.html + */ +#define NGTCP2_PROTO_VER_V2_DRAFT ((uint32_t)0x709a50c4u) + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_DRAFT_MAX` is the maximum QUIC draft + * version that this library supports. + */ +#define NGTCP2_PROTO_VER_DRAFT_MAX 0xff000020u + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_DRAFT_MIN` is the minimum QUIC draft + * version that this library supports. + */ +#define NGTCP2_PROTO_VER_DRAFT_MIN 0xff00001du + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_MAX` is the highest QUIC version that this + * library supports. + */ +#define NGTCP2_PROTO_VER_MAX NGTCP2_PROTO_VER_V1 + +/** + * @macro + * + * :macro:`NGTCP2_PROTO_VER_MIN` is the lowest QUIC version that this + * library supports. + */ +#define NGTCP2_PROTO_VER_MIN NGTCP2_PROTO_VER_DRAFT_MIN + +/** + * @macro + * + * :macro:`NGTCP2_RESERVED_VERSION_MASK` is the bit mask of reserved + * version. + */ +#define NGTCP2_RESERVED_VERSION_MASK 0x0a0a0a0au + +/** + * @macrosection + * + * UDP datagram related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE` is the default maximum UDP + * datagram payload size that this endpoint transmits. + */ +#define NGTCP2_MAX_UDP_PAYLOAD_SIZE 1200 + +/** + * @macro + * + * :macro:`NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE` is the maximum UDP + * datagram payload size that Path MTU Discovery can discover. + */ +#define NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE 1452 + +/** + * @macrosection + * + * QUIC specific macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_MAX_VARINT` is the maximum value which can be + * encoded in variable-length integer encoding. + */ +#define NGTCP2_MAX_VARINT ((1ULL << 62) - 1) + +/** + * @macro + * + * :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` is the length of Stateless + * Reset Token. + */ +#define NGTCP2_STATELESS_RESET_TOKENLEN 16 + +/** + * @macro + * + * :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN` is the minimum length + * of random bytes (Unpredictable Bits) in Stateless Reset packet + */ +#define NGTCP2_MIN_STATELESS_RESET_RANDLEN 5 + +/** + * @macro + * + * :macro:`NGTCP2_PATH_CHALLENGE_DATALEN` is the length of + * PATH_CHALLENGE data. + */ +#define NGTCP2_PATH_CHALLENGE_DATALEN 8 + +/** + * @macro + * + * :macro:`NGTCP2_RETRY_KEY_DRAFT` is an encryption key to create + * integrity tag of Retry packet. It is used for QUIC draft versions. + */ +#define NGTCP2_RETRY_KEY_DRAFT \ + "\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1" + +/** + * @macro + * + * :macro:`NGTCP2_RETRY_NONCE_DRAFT` is nonce used when generating + * integrity tag of Retry packet. It is used for QUIC draft versions. + */ +#define NGTCP2_RETRY_NONCE_DRAFT \ + "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c" + +/** + * @macro + * + * :macro:`NGTCP2_RETRY_KEY_V1` is an encryption key to create + * integrity tag of Retry packet. It is used for QUIC v1. + */ +#define NGTCP2_RETRY_KEY_V1 \ + "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e" + +/** + * @macro + * + * :macro:`NGTCP2_RETRY_NONCE_V1` is nonce used when generating integrity + * tag of Retry packet. It is used for QUIC v1. + */ +#define NGTCP2_RETRY_NONCE_V1 "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb" + +/** + * @macro + * + * :macro:`NGTCP2_RETRY_KEY_V2_DRAFT` is an encryption key to create + * integrity tag of Retry packet. It is used for QUIC v2 draft. + * + * https://quicwg.org/quic-v2/draft-ietf-quic-v2.html + */ +#define NGTCP2_RETRY_KEY_V2_DRAFT \ + "\xba\x85\x8d\xc7\xb4\x3d\xe5\xdb\xf8\x76\x17\xff\x4a\xb2\x53\xdb" + +/** + * @macro + * + * :macro:`NGTCP2_RETRY_NONCE_V2_DRAFT` is nonce used when generating + * integrity tag of Retry packet. It is used for QUIC v2 draft. + * + * https://quicwg.org/quic-v2/draft-ietf-quic-v2.html + */ +#define NGTCP2_RETRY_NONCE_V2_DRAFT \ + "\x14\x1b\x99\xc2\x39\xb0\x3e\x78\x5d\x6a\x2e\x9f" + +/** + * @macro + * + * :macro:`NGTCP2_HP_MASKLEN` is the length of header protection mask. + */ +#define NGTCP2_HP_MASKLEN 5 + +/** + * @macro + * + * :macro:`NGTCP2_HP_SAMPLELEN` is the number bytes sampled when + * encrypting a packet header. + */ +#define NGTCP2_HP_SAMPLELEN 16 + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_INITIAL_RTT` is a default initial RTT. + */ +#define NGTCP2_DEFAULT_INITIAL_RTT (333 * NGTCP2_MILLISECONDS) + +/** + * @macro + * + * :macro:`NGTCP2_MAX_CIDLEN` is the maximum length of Connection ID. + */ +#define NGTCP2_MAX_CIDLEN 20 + +/** + * @macro + * + * :macro:`NGTCP2_MIN_CIDLEN` is the minimum length of Connection ID. + */ +#define NGTCP2_MIN_CIDLEN 1 + +/** + * @macro + * + * :macro:`NGTCP2_MIN_INITIAL_DCIDLEN` is the minimum length of + * Destination Connection ID in Client Initial packet if it does not + * bear token from Retry packet. + */ +#define NGTCP2_MIN_INITIAL_DCIDLEN 8 + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT` is the default handshake + * timeout. + */ +#define NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT (10 * NGTCP2_SECONDS) + +/** + * @macrosection + * + * ECN related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_ECN_NOT_ECT` indicates no ECN marking. + */ +#define NGTCP2_ECN_NOT_ECT 0x0 + +/** + * @macro + * + * :macro:`NGTCP2_ECN_ECT_1` is ECT(1) codepoint. + */ +#define NGTCP2_ECN_ECT_1 0x1 + +/** + * @macro + * + * :macro:`NGTCP2_ECN_ECT_0` is ECT(0) codepoint. + */ +#define NGTCP2_ECN_ECT_0 0x2 + +/** + * @macro + * + * :macro:`NGTCP2_ECN_CE` is CE codepoint. + */ +#define NGTCP2_ECN_CE 0x3 + +/** + * @macro + * + * :macro:`NGTCP2_ECN_MASK` is a bit mask to get ECN marking. + */ +#define NGTCP2_ECN_MASK 0x3 + +#define NGTCP2_PKT_INFO_VERSION_V1 1 +#define NGTCP2_PKT_INFO_VERSION NGTCP2_PKT_INFO_VERSION_V1 + +/** + * @struct + * + * :type:`ngtcp2_pkt_info` is a packet metadata. + */ +typedef struct NGTCP2_ALIGN(8) ngtcp2_pkt_info { + /** + * :member:`ecn` is ECN marking and when passing + * `ngtcp2_conn_read_pkt()`, and it should be either + * :macro:`NGTCP2_ECN_NOT_ECT`, :macro:`NGTCP2_ECN_ECT_1`, + * :macro:`NGTCP2_ECN_ECT_0`, or :macro:`NGTCP2_ECN_CE`. + */ + uint32_t ecn; +} ngtcp2_pkt_info; + +/** + * @macrosection + * + * ngtcp2 library error codes + */ + +/** + * @macro + * + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` indicates that a passed + * argument is invalid. + */ +#define NGTCP2_ERR_INVALID_ARGUMENT -201 +/** + * @macro + * + * :macro:`NGTCP2_ERR_NOBUF` indicates that a provided buffer does not + * have enough space to store data. + */ +#define NGTCP2_ERR_NOBUF -203 +/** + * @macro + * + * :macro:`NGTCP2_ERR_PROTO` indicates a general protocol error. + */ +#define NGTCP2_ERR_PROTO -205 +/** + * @macro + * + * :macro:`NGTCP2_ERR_INVALID_STATE` indicates that a requested + * operation is not allowed at the current connection state. + */ +#define NGTCP2_ERR_INVALID_STATE -206 +/** + * @macro + * + * :macro:`NGTCP2_ERR_ACK_FRAME` indicates that an invalid ACK frame + * is received. + */ +#define NGTCP2_ERR_ACK_FRAME -207 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` indicates that there is no + * spare stream ID available. + */ +#define NGTCP2_ERR_STREAM_ID_BLOCKED -208 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_IN_USE` indicates that a stream ID is + * already in use. + */ +#define NGTCP2_ERR_STREAM_IN_USE -209 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED` indicates that stream data + * cannot be sent because of flow control. + */ +#define NGTCP2_ERR_STREAM_DATA_BLOCKED -210 +/** + * @macro + * + * :macro:`NGTCP2_ERR_FLOW_CONTROL` indicates flow control error. + */ +#define NGTCP2_ERR_FLOW_CONTROL -211 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CONNECTION_ID_LIMIT` indicates that the number + * of received Connection ID exceeds acceptable limit. + */ +#define NGTCP2_ERR_CONNECTION_ID_LIMIT -212 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_LIMIT` indicates that a remote endpoint + * opens more streams that is permitted. + */ +#define NGTCP2_ERR_STREAM_LIMIT -213 +/** + * @macro + * + * :macro:`NGTCP2_ERR_FINAL_SIZE` indicates that inconsistent final + * size of a stream. + */ +#define NGTCP2_ERR_FINAL_SIZE -214 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CRYPTO` indicates crypto (TLS) related error. + */ +#define NGTCP2_ERR_CRYPTO -215 +/** + * @macro + * + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` indicates that packet number + * is exhausted. + */ +#define NGTCP2_ERR_PKT_NUM_EXHAUSTED -216 +/** + * @macro + * + * :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM` indicates that a + * required transport parameter is missing. + */ +#define NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM -217 +/** + * @macro + * + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` indicates that a + * transport parameter is malformed. + */ +#define NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM -218 +/** + * @macro + * + * :macro:`NGTCP2_ERR_FRAME_ENCODING` indicates there is an error in + * frame encoding. + */ +#define NGTCP2_ERR_FRAME_ENCODING -219 +/** + * @macro + * + * :macro:`NGTCP2_ERR_DECRYPT` indicates a decryption failure. + */ +#define NGTCP2_ERR_DECRYPT -220 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_SHUT_WR` indicates no more data can be + * sent to a stream. + */ +#define NGTCP2_ERR_STREAM_SHUT_WR -221 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` indicates that a stream was not + * found. + */ +#define NGTCP2_ERR_STREAM_NOT_FOUND -222 +/** + * @macro + * + * :macro:`NGTCP2_ERR_STREAM_STATE` indicates that a requested + * operation is not allowed at the current stream state. + */ +#define NGTCP2_ERR_STREAM_STATE -226 +/** + * @macro + * + * :macro:`NGTCP2_ERR_RECV_VERSION_NEGOTIATION` indicates that Version + * Negotiation packet was received. + */ +#define NGTCP2_ERR_RECV_VERSION_NEGOTIATION -229 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CLOSING` indicates that connection is in closing + * state. + */ +#define NGTCP2_ERR_CLOSING -230 +/** + * @macro + * + * :macro:`NGTCP2_ERR_DRAINING` indicates that connection is in + * draining state. + */ +#define NGTCP2_ERR_DRAINING -231 +/** + * @macro + * + * :macro:`NGTCP2_ERR_TRANSPORT_PARAM` indicates a general transport + * parameter error. + */ +#define NGTCP2_ERR_TRANSPORT_PARAM -234 +/** + * @macro + * + * :macro:`NGTCP2_ERR_DISCARD_PKT` indicates a packet was discarded. + */ +#define NGTCP2_ERR_DISCARD_PKT -235 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CONN_ID_BLOCKED` indicates that there is no + * spare Connection ID available. + */ +#define NGTCP2_ERR_CONN_ID_BLOCKED -237 +/** + * @macro + * + * :macro:`NGTCP2_ERR_INTERNAL` indicates an internal error. + */ +#define NGTCP2_ERR_INTERNAL -238 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED` indicates that a crypto + * buffer exceeded. + */ +#define NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED -239 +/** + * @macro + * + * :macro:`NGTCP2_ERR_WRITE_MORE` indicates + * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` is used and a function call + * succeeded. + */ +#define NGTCP2_ERR_WRITE_MORE -240 +/** + * @macro + * + * :macro:`NGTCP2_ERR_RETRY` indicates that server should send Retry + * packet. + */ +#define NGTCP2_ERR_RETRY -241 +/** + * @macro + * + * :macro:`NGTCP2_ERR_DROP_CONN` indicates that an endpoint should + * drop connection immediately. + */ +#define NGTCP2_ERR_DROP_CONN -242 +/** + * @macro + * + * :macro:`NGTCP2_ERR_AEAD_LIMIT_REACHED` indicates AEAD encryption + * limit is reached and key update is not available. An endpoint + * should drop connection immediately. + */ +#define NGTCP2_ERR_AEAD_LIMIT_REACHED -243 +/** + * @macro + * + * :macro:`NGTCP2_ERR_NO_VIABLE_PATH` indicates that path validation + * could not probe that a path is capable of sending UDP datagram + * payload of size at least 1200 bytes. + */ +#define NGTCP2_ERR_NO_VIABLE_PATH -244 +/** + * @macro + * + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION` indicates that server + * should send Version Negotiation packet. + */ +#define NGTCP2_ERR_VERSION_NEGOTIATION -245 +/** + * @macro + * + * :macro:`NGTCP2_ERR_HANDSHAKE_TIMEOUT` indicates that QUIC + * connection is not established before the specified deadline. + */ +#define NGTCP2_ERR_HANDSHAKE_TIMEOUT -246 +/** + * @macro + * + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE` indicates the + * version negotiation failed. + */ +#define NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE -247 +/** + * @macro + * + * :macro:`NGTCP2_ERR_IDLE_CLOSE` indicates the connection should be + * closed silently because of idle timeout. + */ +#define NGTCP2_ERR_IDLE_CLOSE -248 +/** + * @macro + * + * :macro:`NGTCP2_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 NGTCP2_ERR_FATAL -500 +/** + * @macro + * + * :macro:`NGTCP2_ERR_NOMEM` indicates out of memory. + */ +#define NGTCP2_ERR_NOMEM -501 +/** + * @macro + * + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` indicates that user defined + * callback function failed. + */ +#define NGTCP2_ERR_CALLBACK_FAILURE -502 + +/** + * @macrosection + * + * QUIC packet header flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_PKT_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_PKT_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_PKT_FLAG_LONG_FORM` indicates the Long header packet + * header. + */ +#define NGTCP2_PKT_FLAG_LONG_FORM 0x01u + +/** + * @macro + * + * :macro:`NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR` indicates that Fixed Bit + * (aka QUIC bit) is not set. + */ +#define NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR 0x02u + +/** + * @macro + * + * :macro:`NGTCP2_PKT_FLAG_KEY_PHASE` indicates Key Phase bit set. + */ +#define NGTCP2_PKT_FLAG_KEY_PHASE 0x04u + +/** + * @enum + * + * :type:`ngtcp2_pkt_type` defines QUIC version-independent QUIC + * packet types. + */ +typedef enum ngtcp2_pkt_type { + /** + * :enum:`NGTCP2_PKT_VERSION_NEGOTIATION` is defined by libngtcp2 + * for convenience. + */ + NGTCP2_PKT_VERSION_NEGOTIATION = 0x80, + /** + * :enum:`NGTCP2_PKT_STATELESS_RESET` is defined by libngtcp2 for + * convenience. + */ + NGTCP2_PKT_STATELESS_RESET = 0x81, + /** + * :enum:`NGTCP2_PKT_INITIAL` indicates Initial packet. + */ + NGTCP2_PKT_INITIAL = 0x10, + /** + * :enum:`NGTCP2_PKT_0RTT` indicates 0RTT packet. + */ + NGTCP2_PKT_0RTT = 0x11, + /** + * :enum:`NGTCP2_PKT_HANDSHAKE` indicates Handshake packet. + */ + NGTCP2_PKT_HANDSHAKE = 0x12, + /** + * :enum:`NGTCP2_PKT_RETRY` indicates Retry packet. + */ + NGTCP2_PKT_RETRY = 0x13, + /** + * :enum:`NGTCP2_PKT_1RTT` is defined by libngtcp2 for convenience. + */ + NGTCP2_PKT_1RTT = 0x40 +} ngtcp2_pkt_type; + +/** + * @macrosection + * + * QUIC transport error code + */ + +/** + * @macro + * + * :macro:`NGTCP2_NO_ERROR` is QUIC transport error code ``NO_ERROR``. + */ +#define NGTCP2_NO_ERROR 0x0u + +/** + * @macro + * + * :macro:`NGTCP2_INTERNAL_ERROR` is QUIC transport error code + * ``INTERNAL_ERROR``. + */ +#define NGTCP2_INTERNAL_ERROR 0x1u + +/** + * @macro + * + * :macro:`NGTCP2_CONNECTION_REFUSED` is QUIC transport error code + * ``CONNECTION_REFUSED``. + */ +#define NGTCP2_CONNECTION_REFUSED 0x2u + +/** + * @macro + * + * :macro:`NGTCP2_FLOW_CONTROL_ERROR` is QUIC transport error code + * ``FLOW_CONTROL_ERROR``. + */ +#define NGTCP2_FLOW_CONTROL_ERROR 0x3u + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_LIMIT_ERROR` is QUIC transport error code + * ``STREAM_LIMIT_ERROR``. + */ +#define NGTCP2_STREAM_LIMIT_ERROR 0x4u + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_STATE_ERROR` is QUIC transport error code + * ``STREAM_STATE_ERROR``. + */ +#define NGTCP2_STREAM_STATE_ERROR 0x5u + +/** + * @macro + * + * :macro:`NGTCP2_FINAL_SIZE_ERROR` is QUIC transport error code + * ``FINAL_SIZE_ERROR``. + */ +#define NGTCP2_FINAL_SIZE_ERROR 0x6u + +/** + * @macro + * + * :macro:`NGTCP2_FRAME_ENCODING_ERROR` is QUIC transport error code + * ``FRAME_ENCODING_ERROR``. + */ +#define NGTCP2_FRAME_ENCODING_ERROR 0x7u + +/** + * @macro + * + * :macro:`NGTCP2_TRANSPORT_PARAMETER_ERROR` is QUIC transport error + * code ``TRANSPORT_PARAMETER_ERROR``. + */ +#define NGTCP2_TRANSPORT_PARAMETER_ERROR 0x8u + +/** + * @macro + * + * :macro:`NGTCP2_CONNECTION_ID_LIMIT_ERROR` is QUIC transport error + * code ``CONNECTION_ID_LIMIT_ERROR``. + */ +#define NGTCP2_CONNECTION_ID_LIMIT_ERROR 0x9u + +/** + * @macro + * + * :macro:`NGTCP2_PROTOCOL_VIOLATION` is QUIC transport error code + * ``PROTOCOL_VIOLATION``. + */ +#define NGTCP2_PROTOCOL_VIOLATION 0xau + +/** + * @macro + * + * :macro:`NGTCP2_INVALID_TOKEN` is QUIC transport error code + * ``INVALID_TOKEN``. + */ +#define NGTCP2_INVALID_TOKEN 0xbu + +/** + * @macro + * + * :macro:`NGTCP2_APPLICATION_ERROR` is QUIC transport error code + * ``APPLICATION_ERROR``. + */ +#define NGTCP2_APPLICATION_ERROR 0xcu + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_BUFFER_EXCEEDED` is QUIC transport error code + * ``CRYPTO_BUFFER_EXCEEDED``. + */ +#define NGTCP2_CRYPTO_BUFFER_EXCEEDED 0xdu + +/** + * @macro + * + * :macro:`NGTCP2_KEY_UPDATE_ERROR` is QUIC transport error code + * ``KEY_UPDATE_ERROR``. + */ +#define NGTCP2_KEY_UPDATE_ERROR 0xeu + +/** + * @macro + * + * :macro:`NGTCP2_AEAD_LIMIT_REACHED` is QUIC transport error code + * ``AEAD_LIMIT_REACHED``. + */ +#define NGTCP2_AEAD_LIMIT_REACHED 0xfu + +/** + * @macro + * + * :macro:`NGTCP2_NO_VIABLE_PATH` is QUIC transport error code + * ``NO_VIABLE_PATH``. + */ +#define NGTCP2_NO_VIABLE_PATH 0x10u + +/** + * @macro + * + * :macro:`NGTCP2_CRYPTO_ERROR` is QUIC transport error code + * ``CRYPTO_ERROR``. + */ +#define NGTCP2_CRYPTO_ERROR 0x100u + +/** + * @macro + * + * :macro:`NGTCP2_VERSION_NEGOTIATION_ERROR_DRAFT` is QUIC transport + * error code ``VERSION_NEGOTIATION_ERROR``. + * + * https://quicwg.org/quic-v2/draft-ietf-quic-v2.html + */ +#define NGTCP2_VERSION_NEGOTIATION_ERROR_DRAFT 0x53f8u + +/** + * @enum + * + * :type:`ngtcp2_path_validation_result` defines path validation + * result code. + */ +typedef enum ngtcp2_path_validation_result { + /** + * :enum:`NGTCP2_PATH_VALIDATION_RESULT_SUCCESS` indicates + * successful validation. + */ + NGTCP2_PATH_VALIDATION_RESULT_SUCCESS, + /** + * :enum:`NGTCP2_PATH_VALIDATION_RESULT_FAILURE` indicates + * validation failure. + */ + NGTCP2_PATH_VALIDATION_RESULT_FAILURE, + /** + * :enum:`NGTCP2_PATH_VALIDATION_RESULT_ABORTED` indicates that path + * validation was aborted. + */ + NGTCP2_PATH_VALIDATION_RESULT_ABORTED +} ngtcp2_path_validation_result; + +/** + * @typedef + * + * :type:`ngtcp2_tstamp` is a timestamp with nanosecond resolution. + * ``UINT64_MAX`` is an invalid value. + */ +typedef uint64_t ngtcp2_tstamp; + +/** + * @typedef + * + * :type:`ngtcp2_duration` is a period of time in nanosecond + * resolution. ``UINT64_MAX`` is an invalid value. + */ +typedef uint64_t ngtcp2_duration; + +/** + * @struct + * + * :type:`ngtcp2_cid` holds a Connection ID. + */ +typedef struct ngtcp2_cid { + /** + * :member:`datalen` is the length of Connection ID. + */ + size_t datalen; + /** + * :member:`data` is the buffer to store Connection ID. + */ + uint8_t data[NGTCP2_MAX_CIDLEN]; +} ngtcp2_cid; + +/** + * @struct + * + * :type:`ngtcp2_vec` is struct iovec compatible structure to + * reference arbitrary array of bytes. + */ +typedef struct ngtcp2_vec { + /** + * :member:`base` points to the data. + */ + uint8_t *base; + /** + * :member:`len` is the number of bytes which the buffer pointed by + * base contains. + */ + size_t len; +} ngtcp2_vec; + +/** + * @function + * + * `ngtcp2_cid_init` initializes Connection ID |cid| with the byte + * string pointed by |data| and its length is |datalen|. |datalen| + * must be at most :macro:`NGTCP2_MAX_CIDLEN`. + */ +NGTCP2_EXTERN void ngtcp2_cid_init(ngtcp2_cid *cid, const uint8_t *data, + size_t datalen); + +/** + * @function + * + * `ngtcp2_cid_eq` returns nonzero if |a| and |b| share the same + * Connection ID. + */ +NGTCP2_EXTERN int ngtcp2_cid_eq(const ngtcp2_cid *a, const ngtcp2_cid *b); + +/** + * @struct + * + * :type:`ngtcp2_pkt_hd` represents QUIC packet header. + */ +typedef struct ngtcp2_pkt_hd { + /** + * :member:`dcid` is Destination Connection ID. + */ + ngtcp2_cid dcid; + /** + * :member:`scid` is Source Connection ID. + */ + ngtcp2_cid scid; + /** + * :member:`pkt_num` is a packet number. + */ + int64_t pkt_num; + /** + * :member:`token` contains token for Initial + * packet. + */ + ngtcp2_vec token; + /** + * :member:`pkt_numlen` is the number of bytes spent to encode + * :member:`pkt_num`. + */ + size_t pkt_numlen; + /** + * :member:`len` is the sum of :member:`pkt_numlen` and the length + * of QUIC packet payload. + */ + size_t len; + /** + * :member:`version` is QUIC version. + */ + uint32_t version; + /** + * :member:`type` is a type of QUIC packet. See + * :type:`ngtcp2_pkt_type`. + */ + uint8_t type; + /** + * :member:`flags` is zero or more of :macro:`NGTCP2_PKT_FLAG_* + * <NGTCP2_PKT_FLAG_NONE>`. + */ + uint8_t flags; +} ngtcp2_pkt_hd; + +/** + * @struct + * + * :type:`ngtcp2_pkt_stateless_reset` represents Stateless Reset. + */ +typedef struct ngtcp2_pkt_stateless_reset { + /** + * :member:`stateless_reset_token` contains stateless reset token. + */ + uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; + /** + * :member:`rand` points a buffer which contains random bytes + * section. + */ + const uint8_t *rand; + /** + * :member:`randlen` is the number of random bytes. + */ + size_t randlen; +} ngtcp2_pkt_stateless_reset; + +/** + * @enum + * + * :type:`ngtcp2_transport_params_type` defines TLS message type which + * carries transport parameters. + */ +typedef enum ngtcp2_transport_params_type { + /** + * :enum:`NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO` is Client Hello + * TLS message. + */ + NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, + /** + * :enum:`NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS` is + * Encrypted Extensions TLS message. + */ + NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS +} ngtcp2_transport_params_type; + +/** + * @macrosection + * + * QUIC transport parameters related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE` is the default + * value of max_udp_payload_size transport parameter. + */ +#define NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE 65527 + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_ACK_DELAY_EXPONENT` is a default value of + * scaling factor of ACK Delay field in ACK frame. + */ +#define NGTCP2_DEFAULT_ACK_DELAY_EXPONENT 3 + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_MAX_ACK_DELAY` is a default value of the + * maximum amount of time in nanoseconds by which endpoint delays + * sending acknowledgement. + */ +#define NGTCP2_DEFAULT_MAX_ACK_DELAY (25 * NGTCP2_MILLISECONDS) + +/** + * @macro + * + * :macro:`NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT` is the default + * value of active_connection_id_limit transport parameter value if + * omitted. + */ +#define NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT 2 + +/** + * @macro + * + * :macro:`NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1` is TLS + * extension type of quic_transport_parameters. + */ +#define NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_V1 0x39u + +/** + * @macro + * + * :macro:`NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_DRAFT` is TLS + * extension type of quic_transport_parameters used during draft + * development. + */ +#define NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS_DRAFT 0xffa5u + +#ifdef NGTCP2_USE_GENERIC_SOCKADDR +typedef struct ngtcp2_sockaddr { + uint16_t sa_family; + uint8_t sa_data[14]; +} ngtcp2_sockaddr; + +typedef struct ngtcp2_in_addr { + uint32_t s_addr; +} ngtcp2_in_addr; + +typedef struct ngtcp2_sockaddr_in { + uint16_t sin_family; + uint16_t sin_port; + ngtcp2_in_addr sin_addr; + uint8_t sin_zero[8]; +} ngtcp2_sockaddr_in; + +# define NGTCP2_SS_MAXSIZE 128 +# define NGTCP2_SS_ALIGNSIZE (sizeof(uint64_t)) +# define NGTCP2_SS_PAD1SIZE (NGTCP2_SS_ALIGNSIZE - sizeof(uint16_t)) +# define NGTCP2_SS_PAD2SIZE \ + (NGTCP2_SS_MAXSIZE - \ + (sizeof(uint16_t) + NGTCP2_SS_PAD1SIZE + NGTCP2_SS_ALIGNSIZE)) + +typedef struct ngtcp2_sockaddr_storage { + uint16_t ss_family; + uint8_t _ss_pad1[NGTCP2_SS_PAD1SIZE]; + uint64_t _ss_align; + uint8_t _ss_pad2[NGTCP2_SS_PAD2SIZE]; +} ngtcp2_sockaddr_storage; + +# undef NGTCP2_SS_PAD2SIZE +# undef NGTCP2_SS_PAD1SIZE +# undef NGTCP2_SS_ALIGNSIZE +# undef NGTCP2_SS_MAXSIZE + +typedef uint32_t ngtcp2_socklen; +#else +/** + * @typedef + * + * :type:`ngtcp2_sockaddr` is typedefed to struct sockaddr. If + * :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed to + * the generic struct sockaddr defined in ngtcp2.h. + */ +typedef struct sockaddr ngtcp2_sockaddr; +/** + * @typedef + * + * :type:`ngtcp2_sockaddr_storage` is typedefed to struct + * sockaddr_storage. If :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is + * defined, it is typedefed to the generic struct sockaddr_storage + * defined in ngtcp2.h. + */ +typedef struct sockaddr_storage ngtcp2_sockaddr_storage; +/** + * @typedef + * + * :type:`ngtcp2_sockaddr_in` is typedefed to struct sockaddr_in. If + * :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed to + * the generic struct sockaddr_in defined in ngtcp2.h. + */ +typedef struct sockaddr_in ngtcp2_sockaddr_in; +/** + * @typedef + * + * :type:`ngtcp2_socklen` is typedefed to socklen_t. If + * :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed to + * uint32_t. + */ +typedef socklen_t ngtcp2_socklen; +#endif + +#if defined(NGTCP2_USE_GENERIC_SOCKADDR) || \ + defined(NGTCP2_USE_GENERIC_IPV6_SOCKADDR) +typedef struct ngtcp2_in6_addr { + uint8_t in6_addr[16]; +} ngtcp2_in6_addr; + +typedef struct ngtcp2_sockaddr_in6 { + uint16_t sin6_family; + uint16_t sin6_port; + uint32_t sin6_flowinfo; + ngtcp2_in6_addr sin6_addr; + uint32_t sin6_scope_id; +} ngtcp2_sockaddr_in6; +#else +/** + * @typedef + * + * :type:`ngtcp2_sockaddr_in6` is typedefed to struct sockaddr_in6. + * If :macro:`NGTCP2_USE_GENERIC_SOCKADDR` is defined, it is typedefed + * to the generic struct sockaddr_in6 defined in ngtcp2.h. + */ +typedef struct sockaddr_in6 ngtcp2_sockaddr_in6; +#endif + +/** + * @struct + * + * :type:`ngtcp2_sockaddr_union` conveniently includes all supported + * address types. + */ +typedef union ngtcp2_sockaddr_union { + ngtcp2_sockaddr sa; + ngtcp2_sockaddr_in in; + ngtcp2_sockaddr_in6 in6; +} ngtcp2_sockaddr_union; + +/** + * @struct + * + * :type:`ngtcp2_preferred_addr` represents preferred address + * structure. + */ +typedef struct ngtcp2_preferred_addr { + /** + * :member:`cid` is a Connection ID. + */ + ngtcp2_cid cid; + /** + * :member:`ipv4` contains IPv4 address and port. + */ + ngtcp2_sockaddr_in ipv4; + /** + * :member:`ipv6` contains IPv4 address and port. + */ + ngtcp2_sockaddr_in6 ipv6; + /** + * :member:`ipv4_present` indicates that :member:`ipv4` contains + * IPv4 address and port. + */ + uint8_t ipv4_present; + /** + * :member:`ipv6_present` indicates that :member:`ipv6` contains + * IPv6 address and port. + */ + uint8_t ipv6_present; + /** + * :member:`stateless_reset_token` contains stateless reset token. + */ + uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; +} ngtcp2_preferred_addr; + +/** + * @struct + * + * :type:`ngtcp2_version_info` represents version_information + * structure. + */ +typedef struct ngtcp2_version_info { + /** + * :member:`chosen_version` is the version chosen by the sender. + */ + uint32_t chosen_version; + /** + * :member:`other_versions` points the wire image of other_versions + * field. The each version is therefore in network byte order. + */ + uint8_t *other_versions; + /** + * :member:`other_versionslen` is the number of bytes pointed by + * :member:`other_versions`, not the number of versions included. + */ + size_t other_versionslen; +} ngtcp2_version_info; + +#define NGTCP2_TRANSPORT_PARAMS_VERSION_V1 1 +#define NGTCP2_TRANSPORT_PARAMS_VERSION NGTCP2_TRANSPORT_PARAMS_VERSION_V1 + +/** + * @struct + * + * :type:`ngtcp2_transport_params` represents QUIC transport + * parameters. + */ +typedef struct ngtcp2_transport_params { + /** + * :member:`preferred_address` contains preferred address if + * :member:`preferred_address_present` is nonzero. + */ + ngtcp2_preferred_addr preferred_address; + /** + * :member:`original_dcid` is the Destination Connection ID field + * from the first Initial packet from client. Server must specify + * this field. It is expected that application knows the original + * Destination Connection ID even if it sends Retry packet, for + * example, by including it in retry token. Otherwise, application + * should not specify this field. + */ + ngtcp2_cid original_dcid; + /** + * :member:`initial_scid` is the Source Connection ID field from the + * first Initial packet the endpoint sends. Application should not + * specify this field. + */ + ngtcp2_cid initial_scid; + /** + * :member:`retry_scid` is the Source Connection ID field from Retry + * packet. Only server uses this field. If server application + * received Initial packet with retry token from client and server + * verified its token, server application must set Destination + * Connection ID field from the Initial packet to this field and set + * :member:`retry_scid_present` to nonzero. Server application must + * verify that the Destination Connection ID from Initial packet was + * sent in Retry packet by, for example, including the Connection ID + * in a token, or including it in AAD when encrypting a token. + */ + ngtcp2_cid retry_scid; + /** + * :member:`initial_max_stream_data_bidi_local` is the size of flow + * control window of locally initiated stream. This is the number + * of bytes that the remote endpoint can send and the local endpoint + * must ensure that it has enough buffer to receive them. + */ + uint64_t initial_max_stream_data_bidi_local; + /** + * :member:`initial_max_stream_data_bidi_remote` is the size of flow + * control window of remotely initiated stream. This is the number + * of bytes that the remote endpoint can send and the local endpoint + * must ensure that it has enough buffer to receive them. + */ + uint64_t initial_max_stream_data_bidi_remote; + /** + * :member:`initial_max_stream_data_uni` is the size of flow control + * window of remotely initiated unidirectional stream. This is the + * number of bytes that the remote endpoint can send and the local + * endpoint must ensure that it has enough buffer to receive them. + */ + uint64_t initial_max_stream_data_uni; + /** + * :member:`initial_max_data` is the connection level flow control + * window. + */ + uint64_t initial_max_data; + /** + * :member:`initial_max_streams_bidi` is the number of concurrent + * streams that the remote endpoint can create. + */ + uint64_t initial_max_streams_bidi; + /** + * :member:`initial_max_streams_uni` is the number of concurrent + * unidirectional streams that the remote endpoint can create. + */ + uint64_t initial_max_streams_uni; + /** + * :member:`max_idle_timeout` is a duration during which sender + * allows quiescent. + */ + ngtcp2_duration max_idle_timeout; + /** + * :member:`max_udp_payload_size` is the maximum datagram size that + * the endpoint can receive. + */ + uint64_t max_udp_payload_size; + /** + * :member:`active_connection_id_limit` is the maximum number of + * Connection ID that sender can store. + */ + uint64_t active_connection_id_limit; + /** + * :member:`ack_delay_exponent` is the exponent used in ACK Delay + * field in ACK frame. + */ + uint64_t ack_delay_exponent; + /** + * :member:`max_ack_delay` is the maximum acknowledgement delay by + * which the endpoint will delay sending acknowledgements. + */ + ngtcp2_duration max_ack_delay; + /** + * :member:`max_datagram_frame_size` is the maximum size of DATAGRAM + * frame that this endpoint willingly receives. Specifying 0 + * disables DATAGRAM support. See + * https://datatracker.ietf.org/doc/html/rfc9221 + */ + uint64_t max_datagram_frame_size; + /** + * :member:`stateless_reset_token_present` is nonzero if + * :member:`stateless_reset_token` field is set. + */ + uint8_t stateless_reset_token_present; + /** + * :member:`disable_active_migration` is nonzero if the endpoint + * does not support active connection migration. + */ + uint8_t disable_active_migration; + /** + * :member:`retry_scid_present` is nonzero if :member:`retry_scid` + * field is set. + */ + uint8_t retry_scid_present; + /** + * :member:`preferred_address_present` is nonzero if + * :member:`preferred_address` is set. + */ + uint8_t preferred_address_present; + /** + * :member:`stateless_reset_token` contains stateless reset token. + */ + uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; + /** + * :member:`grease_quic_bit` is nonzero if sender supports "Greasing + * the QUIC Bit" extension. See :rfc:`9287`. Note that the local + * endpoint always enables greasing QUIC bit regardless of this + * field value. + */ + uint8_t grease_quic_bit; + /** + * :member:`version_info` contains version_information field if + * :member:`version_info_present` is nonzero. Application should + * not specify this field. + */ + ngtcp2_version_info version_info; + /** + * :member:`version_info_present` is nonzero if + * :member:`version_info` is set. Application should not specify + * this field. + */ + uint8_t version_info_present; +} ngtcp2_transport_params; + +/** + * @enum + * + * :type:`ngtcp2_pktns_id` defines packet number space identifier. + */ +typedef enum ngtcp2_pktns_id { + /** + * :enum:`NGTCP2_PKTNS_ID_INITIAL` is the Initial packet number + * space. + */ + NGTCP2_PKTNS_ID_INITIAL, + /** + * :enum:`NGTCP2_PKTNS_ID_HANDSHAKE` is the Handshake packet number + * space. + */ + NGTCP2_PKTNS_ID_HANDSHAKE, + /** + * :enum:`NGTCP2_PKTNS_ID_APPLICATION` is the Application data + * packet number space. + */ + NGTCP2_PKTNS_ID_APPLICATION, + /** + * :enum:`NGTCP2_PKTNS_ID_MAX` is defined to get the number of + * packet number spaces. + */ + NGTCP2_PKTNS_ID_MAX +} ngtcp2_pktns_id; + +#define NGTCP2_CONN_STAT_VERSION_V1 1 +#define NGTCP2_CONN_STAT_VERSION NGTCP2_CONN_STAT_VERSION_V1 + +/** + * @struct + * + * :type:`ngtcp2_conn_stat` holds various connection statistics, and + * computed data for recovery and congestion controller. + */ +typedef struct ngtcp2_conn_stat { + /** + * :member:`latest_rtt` is the latest RTT sample which is not + * adjusted by acknowledgement delay. + */ + ngtcp2_duration latest_rtt; + /** + * :member:`min_rtt` is the minimum RTT seen so far. It is not + * adjusted by acknowledgement delay. + */ + ngtcp2_duration min_rtt; + /** + * :member:`smoothed_rtt` is the smoothed RTT. + */ + ngtcp2_duration smoothed_rtt; + /** + * :member:`rttvar` is a mean deviation of observed RTT. + */ + ngtcp2_duration rttvar; + /** + * :member:`initial_rtt` is the initial RTT which is used when no + * RTT sample is available. + */ + ngtcp2_duration initial_rtt; + /** + * :member:`first_rtt_sample_ts` is the timestamp when the first RTT + * sample is obtained. + */ + ngtcp2_tstamp first_rtt_sample_ts; + /** + * :member:`pto_count` is the count of successive PTO timer + * expiration. + */ + size_t pto_count; + /** + * :member:`loss_detection_timer` is the deadline of the current + * loss detection timer. + */ + ngtcp2_tstamp loss_detection_timer; + /** + * :member:`last_tx_pkt_ts` corresponds to + * time_of_last_ack_eliciting_packet in :rfc:`9002`. + */ + ngtcp2_tstamp last_tx_pkt_ts[NGTCP2_PKTNS_ID_MAX]; + /** + * :member:`loss_time` corresponds to loss_time in :rfc:`9002`. + */ + ngtcp2_tstamp loss_time[NGTCP2_PKTNS_ID_MAX]; + /** + * :member:`cwnd` is the size of congestion window. + */ + uint64_t cwnd; + /** + * :member:`ssthresh` is slow start threshold. + */ + uint64_t ssthresh; + /** + * :member:`congestion_recovery_start_ts` is the timestamp when + * congestion recovery started. + */ + ngtcp2_tstamp congestion_recovery_start_ts; + /** + * :member:`bytes_in_flight` is the number in bytes of all sent + * packets which have not been acknowledged. + */ + uint64_t bytes_in_flight; + /** + * :member:`max_tx_udp_payload_size` is the maximum size of UDP + * datagram payload that this endpoint transmits. It is used by + * congestion controller to compute congestion window. + */ + size_t max_tx_udp_payload_size; + /** + * :member:`delivery_rate_sec` is the current sending rate measured + * in byte per second. + */ + uint64_t delivery_rate_sec; + /** + * :member:`pacing_rate` is the current packet sending rate computed + * by a congestion controller. 0 if a congestion controller does + * not set pacing rate. Even if this value is set to 0, the library + * paces packets. + */ + double pacing_rate; + /** + * :member:`send_quantum` is the maximum size of a data aggregate + * scheduled and transmitted together. + */ + size_t send_quantum; +} ngtcp2_conn_stat; + +/** + * @enum + * + * :type:`ngtcp2_cc_algo` defines congestion control algorithms. + */ +typedef enum ngtcp2_cc_algo { + /** + * :enum:`NGTCP2_CC_ALGO_RENO` represents Reno. + */ + NGTCP2_CC_ALGO_RENO = 0x00, + /** + * :enum:`NGTCP2_CC_ALGO_CUBIC` represents Cubic. + */ + NGTCP2_CC_ALGO_CUBIC = 0x01, + /** + * :enum:`NGTCP2_CC_ALGO_BBR` represents BBR. + */ + NGTCP2_CC_ALGO_BBR = 0x02, + /** + * :enum:`NGTCP2_CC_ALGO_BBR2` represents BBR v2. + */ + NGTCP2_CC_ALGO_BBR2 = 0x03 +} ngtcp2_cc_algo; + +/** + * @functypedef + * + * :type:`ngtcp2_printf` is a callback function for logging. + * |user_data| is the same object passed to `ngtcp2_conn_client_new` + * or `ngtcp2_conn_server_new`. + */ +typedef void (*ngtcp2_printf)(void *user_data, const char *format, ...); + +/** + * @macrosection + * + * QLog related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_QLOG_WRITE_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_QLOG_WRITE_FLAG_NONE 0x00u +/** + * @macro + * + * :macro:`NGTCP2_QLOG_WRITE_FLAG_FIN` indicates that this is the + * final call to :type:`ngtcp2_qlog_write` in the current connection. + */ +#define NGTCP2_QLOG_WRITE_FLAG_FIN 0x01u + +/** + * @struct + * + * :type:`ngtcp2_rand_ctx` is a wrapper around native random number + * generator. It is opaque to the ngtcp2 library. This might be + * useful if application needs to specify random number generator per + * thread or per connection. + */ +typedef struct ngtcp2_rand_ctx { + /** + * :member:`native_handle` is a pointer to an underlying random + * number generator. + */ + void *native_handle; +} ngtcp2_rand_ctx; + +/** + * @functypedef + * + * :type:`ngtcp2_qlog_write` is a callback function which is called to + * write qlog |data| of length |datalen| bytes. |flags| is bitwise OR + * of zero or more of :macro:`NGTCP2_QLOG_WRITE_FLAG_* + * <NGTCP2_QLOG_WRITE_FLAG_NONE>`. If + * :macro:`NGTCP2_QLOG_WRITE_FLAG_FIN` is set, |datalen| may be 0. + */ +typedef void (*ngtcp2_qlog_write)(void *user_data, uint32_t flags, + const void *data, size_t datalen); + +/** + * @struct + * + * :type:`ngtcp2_qlog_settings` is a set of settings for qlog. + */ +typedef struct ngtcp2_qlog_settings { + /** + * :member:`odcid` is Original Destination Connection ID sent by + * client. It is used as group_id and ODCID fields. Client ignores + * this field and uses dcid parameter passed to + * `ngtcp2_conn_client_new()`. + */ + ngtcp2_cid odcid; + /** + * :member:`write` is a callback function to write qlog. Setting + * ``NULL`` disables qlog. + */ + ngtcp2_qlog_write write; +} ngtcp2_qlog_settings; + +#define NGTCP2_SETTINGS_VERSION_V1 1 +#define NGTCP2_SETTINGS_VERSION NGTCP2_SETTINGS_VERSION_V1 + +/** + * @struct + * + * :type:`ngtcp2_settings` defines QUIC connection settings. + */ +typedef struct ngtcp2_settings { + /** + * :member:`qlog` is qlog settings. + */ + ngtcp2_qlog_settings qlog; + /** + * :member:`cc_algo` specifies congestion control algorithm. + */ + ngtcp2_cc_algo cc_algo; + /** + * :member:`initial_ts` is an initial timestamp given to the + * library. + */ + ngtcp2_tstamp initial_ts; + /** + * :member:`initial_rtt` is an initial RTT. + */ + ngtcp2_duration initial_rtt; + /** + * :member:`log_printf` is a function that the library uses to write + * logs. ``NULL`` means no logging output. It is nothing to do + * with qlog. + */ + ngtcp2_printf log_printf; + /** + * :member:`max_tx_udp_payload_size` is the maximum size of UDP + * datagram payload that this endpoint transmits. It is used by + * congestion controller to compute congestion window. + */ + size_t max_tx_udp_payload_size; + /** + * :member:`token` is a token from Retry packet or NEW_TOKEN frame. + * + * Server sets this field if it received the token in Client Initial + * packet and successfully validated. + * + * Client sets this field if it intends to send token in its Initial + * packet. + * + * `ngtcp2_conn_server_new` and `ngtcp2_conn_client_new` make a copy + * of token. + */ + ngtcp2_vec token; + /** + * :member:`rand_ctx` is an optional random number generator to be + * passed to :type:`ngtcp2_rand` callback. + */ + ngtcp2_rand_ctx rand_ctx; + /** + * :member:`max_window` is the maximum connection-level flow control + * window if connection-level window auto-tuning is enabled. The + * connection-level window auto tuning is enabled if nonzero value + * is specified in this field. The initial value of window size is + * :member:`ngtcp2_transport_params.initial_max_data`. The window + * size is scaled up to the value specified in this field. + */ + uint64_t max_window; + /** + * :member:`max_stream_window` is the maximum stream-level flow + * control window if stream-level window auto-tuning is enabled. + * The stream-level window auto-tuning is enabled if nonzero value + * is specified in this field. The initial value of window size is + * :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_remote`, + * :member:`ngtcp2_transport_params.initial_max_stream_data_bidi_local`, + * or :member:`ngtcp2_transport_params.initial_max_stream_data_uni`, + * depending on the type of stream. The window size is scaled up to + * the value specified in this field. + */ + uint64_t max_stream_window; + /** + * :member:`ack_thresh` is the minimum number of the received ACK + * eliciting packets that triggers the immediate acknowledgement. + */ + size_t ack_thresh; + /** + * :member:`no_tx_udp_payload_size_shaping`, if set to nonzero, + * instructs the library not to limit the UDP payload size to + * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE` (which can be extended by + * Path MTU Discovery) and instead use the mininum size among the + * given buffer size, :member:`max_tx_udp_payload_size`, and the + * received max_udp_payload QUIC transport parameter. + */ + int no_tx_udp_payload_size_shaping; + /** + * :member:`handshake_timeout` is the period of time before giving + * up QUIC connection establishment. If QUIC handshake is not + * complete within this period, `ngtcp2_conn_handle_expiry` returns + * :macro:`NGTCP2_ERR_HANDSHAKE_TIMEOUT` error. The deadline is + * :member:`initial_ts` + :member:`handshake_timeout`. If this + * field is set to ``UINT64_MAX``, no handshake timeout is set. + */ + ngtcp2_duration handshake_timeout; + /** + * :member:`preferred_versions` is the array of versions that are + * preferred by the local endpoint. All versions set in this array + * must be supported by the library, and compatible to QUIC v1. The + * reserved versions are not allowed. They are sorted in the order + * of preference. + * + * On compatible version negotiation, server will negotiate one of + * those versions contained in this array if a client initially + * chooses a less preferred version. This version set corresponds + * to Offered Versions in QUIC Version Negotiation draft, and it should + * be sent in Version Negotiation packet. + * + * Client uses this field and :member:`original_version` to prevent + * version downgrade attack if it reacted upon Version Negotiation + * packet. If this field is specified, client must include + * |client_chosen_version| passed to `ngtcp2_conn_client_new` unless + * |client_chosen_version| is a reserved version. + */ + uint32_t *preferred_versions; + /** + * :member:`preferred_versionslen` is the number of versions that + * are contained in the array pointed by + * :member:`preferred_versions`. + */ + size_t preferred_versionslen; + /** + * :member:`other_versions` is the array of versions that are set in + * :member:`other_versions <ngtcp2_version_info.other_versions>` + * field of outgoing version_information QUIC transport parameter. + * + * For server, this corresponds to Fully-Deployed Versions in QUIC + * Version Negotiation draft. If this field is set not, it is set + * to :member:`preferred_versions` internally if + * :member:`preferred_versionslen` is not zero. If this field is + * not set, and :member:`preferred_versionslen` is zero, this field + * is set to :macro:`NGTCP2_PROTO_VER_V1` internally. + * + * Client must include |client_chosen_version| passed to + * `ngtcp2_conn_client_new` in this array if this field is set and + * |client_chosen_version| is not a reserved version. If this field + * is not set, |client_chosen_version| passed to + * `ngtcp2_conn_client_new` will be set in this field internally + * unless |client_chosen_version| is a reserved version. + */ + uint32_t *other_versions; + /** + * :member:`other_versionslen` is the number of versions that are + * contained in the array pointed by :member:`other_versions`. + */ + size_t other_versionslen; + /** + * :member:`original_version` is the original version that client + * initially used to make a connection attempt. If it is set, and + * it differs from |client_chosen_version| passed to + * `ngtcp2_conn_client_new`, the library assumes that client reacted + * upon Version Negotiation packet. Server does not use this field. + */ + uint32_t original_version; + /** + * :member:`no_pmtud`, if set to nonzero, disables Path MTU + * Discovery. + */ + int no_pmtud; +} ngtcp2_settings; + +/** + * @struct + * + * :type:`ngtcp2_addr` is the endpoint address. + */ +typedef struct ngtcp2_addr { + /** + * :member:`addr` points to the buffer which contains endpoint + * address. It must not be ``NULL``. + */ + ngtcp2_sockaddr *addr; + /** + * :member:`addrlen` is the length of addr. + */ + ngtcp2_socklen addrlen; +} ngtcp2_addr; + +/** + * @struct + * + * :type:`ngtcp2_path` is the network endpoints where a packet is sent + * and received. + */ +typedef struct ngtcp2_path { + /** + * :member:`local` is the address of local endpoint. + */ + ngtcp2_addr local; + /** + * :member:`remote` is the address of remote endpoint. + */ + ngtcp2_addr remote; + /** + * :member:`user_data` is an arbitrary data and opaque to the + * library. + * + * Note that :type:`ngtcp2_path` is generally passed to + * :type:`ngtcp2_conn` by an application, and :type:`ngtcp2_conn` + * stores their copies. Unfortunately, there is no way for the + * application to know when :type:`ngtcp2_conn` finishes using a + * specific :type:`ngtcp2_path` object in mid connection, which + * means that the application cannot free the data pointed by this + * field. Therefore, it is advised to use this field only when the + * data pointed by this field persists in an entire lifetime of the + * connection. + */ + void *user_data; +} ngtcp2_path; + +/** + * @struct + * + * :type:`ngtcp2_path_storage` is a convenient struct to have buffers + * to store the longest addresses. + */ +typedef struct ngtcp2_path_storage { + /** + * :member:`path` stores network path. + */ + ngtcp2_path path; + /** + * :member:`local_addrbuf` is a buffer to store local address. + */ + ngtcp2_sockaddr_union local_addrbuf; + /** + * :member:`remote_addrbuf` is a buffer to store remote address. + */ + ngtcp2_sockaddr_union remote_addrbuf; +} ngtcp2_path_storage; + +/** + * @struct + * + * :type:`ngtcp2_crypto_md` is a wrapper around native message digest + * object. + */ +typedef struct ngtcp2_crypto_md { + /** + * :member:`native_handle` is a pointer to an underlying message + * digest object. + */ + void *native_handle; +} ngtcp2_crypto_md; + +/** + * @struct + * + * :type:`ngtcp2_crypto_aead` is a wrapper around native AEAD object. + */ +typedef struct ngtcp2_crypto_aead { + /** + * :member:`native_handle` is a pointer to an underlying AEAD + * object. + */ + void *native_handle; + /** + * :member:`max_overhead` is the number of additional bytes which + * AEAD encryption needs on encryption. + */ + size_t max_overhead; +} ngtcp2_crypto_aead; + +/** + * @struct + * + * :type:`ngtcp2_crypto_cipher` is a wrapper around native cipher + * object. + */ +typedef struct ngtcp2_crypto_cipher { + /** + * :member:`native_handle` is a pointer to an underlying cipher + * object. + */ + void *native_handle; +} ngtcp2_crypto_cipher; + +/** + * @struct + * + * :type:`ngtcp2_crypto_aead_ctx` is a wrapper around native AEAD + * cipher context object. It should be initialized with a specific + * key. ngtcp2 library reuses this context object to encrypt or + * decrypt multiple packets. + */ +typedef struct ngtcp2_crypto_aead_ctx { + /** + * :member:`native_handle` is a pointer to an underlying AEAD + * context object. + */ + void *native_handle; +} ngtcp2_crypto_aead_ctx; + +/** + * @struct + * + * :type:`ngtcp2_crypto_cipher_ctx` is a wrapper around native cipher + * context object. It should be initialized with a specific key. + * ngtcp2 library reuses this context object to encrypt or decrypt + * multiple packet headers. + */ +typedef struct ngtcp2_crypto_cipher_ctx { + /** + * :member:`native_handle` is a pointer to an underlying cipher + * context object. + */ + void *native_handle; +} ngtcp2_crypto_cipher_ctx; + +/** + * @struct + * + * :type:`ngtcp2_crypto_ctx` is a convenient structure to bind all + * crypto related objects in one place. Use + * `ngtcp2_crypto_ctx_initial` to initialize this struct for Initial + * packet encryption. For Handshake and 1RTT packets, use + * `ngtcp2_crypto_ctx_tls`. + */ +typedef struct ngtcp2_crypto_ctx { + /** + * :member:`aead` is AEAD object. + */ + ngtcp2_crypto_aead aead; + /** + * :member:`md` is message digest object. + */ + ngtcp2_crypto_md md; + /** + * :member:`hp` is header protection cipher. + */ + ngtcp2_crypto_cipher hp; + /** + * :member:`max_encryption` is the number of encryption which this + * key can be used with. + */ + uint64_t max_encryption; + /** + * :member:`max_decryption_failure` is the number of decryption + * failure with this key. + */ + uint64_t max_decryption_failure; +} ngtcp2_crypto_ctx; + +/** + * @function + * + * `ngtcp2_encode_transport_params` encodes |params| in |dest| of + * length |destlen|. + * + * If |dest| is NULL, and |destlen| is zero, this function just + * returns the number of bytes required to store the encoded transport + * parameters. + * + * This function returns the number of written, or one of the + * following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |exttype| is invalid. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_encode_transport_params_versioned( + uint8_t *dest, size_t destlen, ngtcp2_transport_params_type exttype, + int transport_params_version, const ngtcp2_transport_params *params); + +/** + * @function + * + * `ngtcp2_decode_transport_params` decodes transport parameters in + * |data| of length |datalen|, and stores the result in the object + * pointed by |params|. + * + * If the optional parameters are missing, the default value is + * assigned. + * + * The following fields may point to somewhere inside the buffer + * pointed by |data| of length |datalen|: + * + * - :member:`ngtcp2_transport_params.version_info.other_versions + * <ngtcp2_version_info.other_versions>` + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM` + * The required parameter is missing. + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` + * The input is malformed. + */ +NGTCP2_EXTERN int ngtcp2_decode_transport_params_versioned( + int transport_params_version, ngtcp2_transport_params *params, + ngtcp2_transport_params_type exttype, const uint8_t *data, size_t datalen); + +/** + * @function + * + * `ngtcp2_decode_transport_params_new` decodes transport parameters + * in |data| of length |datalen|, and stores the result in the object + * allocated dynamically. The pointer to the allocated object is + * assigned to |*pparams|. Unlike `ngtcp2_decode_transport_params`, + * all direct and indirect fields are also allocated dynamically if + * needed. + * + * |mem| is a memory allocator to allocate memory. If |mem| is + * ``NULL``, the memory allocator returned by `ngtcp2_mem_default()` + * is used. + * + * If the optional parameters are missing, the default value is + * assigned. + * + * `ngtcp2_transport_params_del` frees the memory allocated by this + * function. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM` + * The required parameter is missing. + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` + * The input is malformed. + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_decode_transport_params_new( + ngtcp2_transport_params **pparams, ngtcp2_transport_params_type exttype, + const uint8_t *data, size_t datalen, const ngtcp2_mem *mem); + +/** + * @function + * + * `ngtcp2_transport_params_del` frees the |params| which must be + * dynamically allocated by `ngtcp2_decode_transport_params_new`. + * + * |mem| is a memory allocator that allocated |params|. If |mem| is + * ``NULL``, the memory allocator returned by `ngtcp2_mem_default()` + * is used. + * + * If |params| is ``NULL``, this function does nothing. + */ +NGTCP2_EXTERN void ngtcp2_transport_params_del(ngtcp2_transport_params *params, + const ngtcp2_mem *mem); + +/** + * @struct + * + * :type:`ngtcp2_version_cid` is a convenient struct to store the + * result of `ngtcp2_pkt_decode_version_cid`. + */ +typedef struct ngtcp2_version_cid { + /** + * :member:`version` stores QUIC version. + */ + uint32_t version; + /** + * :member:`dcid` points to the Destination Connection ID. + */ + const uint8_t *dcid; + /** + * :member:`dcidlen` is the length of the Destination Connection ID + * pointed by :member:`dcid`. + */ + size_t dcidlen; + /** + * :member:`scid` points to the Source Connection ID. + */ + const uint8_t *scid; + /** + * :member:`scidlen` is the length of the Source Connection ID + * pointed by :member:`scid`. + */ + size_t scidlen; +} ngtcp2_version_cid; + +/** + * @function + * + * `ngtcp2_pkt_decode_version_cid` extracts QUIC version, Destination + * Connection ID and Source Connection ID from the packet pointed by + * |data| of length |datalen|. This function can handle Connection ID + * up to 255 bytes unlike `ngtcp2_pkt_decode_hd_long` or + * `ngtcp2_pkt_decode_hd_short` which are only capable of handling + * Connection ID less than or equal to :macro:`NGTCP2_MAX_CIDLEN`. + * Longer Connection ID is only valid if the version is unsupported + * QUIC version. + * + * If the given packet is Long header packet, this function extracts + * the version from the packet and assigns it to + * :member:`dest->version <ngtcp2_version_cid.version>`. It also + * extracts the pointer to the Destination Connection ID and its + * length and assigns them to :member:`dest->dcid + * <ngtcp2_version_cid.dcid>` and :member:`dest->dcidlen + * <ngtcp2_version_cid.dcidlen>` respectively. Similarly, it extracts + * the pointer to the Source Connection ID and its length and assigns + * them to :member:`dest->scid <ngtcp2_version_cid.scid>` and + * :member:`dest->scidlen <ngtcp2_version_cid.scidlen>` respectively. + * + * If the given packet is Short header packet, :member:`dest->version + * <ngtcp2_version_cid.version>` will be 0, :member:`dest->scid + * <ngtcp2_version_cid.scid>` will be ``NULL``, and + * :member:`dest->scidlen <ngtcp2_version_cid.scidlen>` will be 0. + * Because the Short header packet does not have the length of + * Destination Connection ID, the caller has to pass the length in + * |short_dcidlen|. This function extracts the pointer to the + * Destination Connection ID and assigns it to :member:`dest->dcid + * <ngtcp2_version_cid.dcid>`. |short_dcidlen| is assigned to + * :member:`dest->dcidlen <ngtcp2_version_cid.dcidlen>`. + * + * If Version Negotiation is required, this function returns + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION`. Unlike the other error + * cases, all fields of |dest| are assigned as described above. + * + * This function returns 0 if it succeeds. Otherwise, one of the + * following negative error code: + * + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * The function could not decode the packet header. + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION` + * Version Negotiation packet should be sent. + */ +NGTCP2_EXTERN int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest, + const uint8_t *data, + size_t datalen, + size_t short_dcidlen); + +/** + * @function + * + * `ngtcp2_pkt_decode_hd_long` decodes QUIC long packet header in + * |pkt| of length |pktlen|. This function only parses the input just + * before packet number field. + * + * This function does not verify that length field is correct. In + * other words, this function succeeds even if length > |pktlen|. + * + * This function can handle Connection ID up to + * :macro:`NGTCP2_MAX_CIDLEN`. Consider to use + * `ngtcp2_pkt_decode_version_cid` to get longer Connection ID. + * + * This function handles Version Negotiation specially. If version + * field is 0, |pkt| must contain Version Negotiation packet. Version + * Negotiation packet has random type in wire format. For + * convenience, this function sets + * :enum:`ngtcp2_pkt_type.NGTCP2_PKT_VERSION_NEGOTIATION` to + * :member:`dest->type <ngtcp2_pkt_hd.type>`, clears + * :macro:`NGTCP2_PKT_FLAG_LONG_FORM` flag from :member:`dest->flags + * <ngtcp2_pkt_hd.flags>`, and sets 0 to :member:`dest->len + * <ngtcp2_pkt_hd.len>`. Version Negotiation packet occupies a single + * packet. + * + * It stores the result in the object pointed by |dest|, and returns + * the number of bytes decoded to read the packet header if it + * succeeds, or one of the following error codes: + * + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * Packet is too short; or it is not a long header + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, + const uint8_t *pkt, + size_t pktlen); + +/** + * @function + * + * `ngtcp2_pkt_decode_hd_short` decodes QUIC short header packet + * header in |pkt| of length |pktlen|. |dcidlen| is the length of + * DCID in packet header. Short header packet does not encode the + * length of connection ID, thus we need the input from the outside. + * This function only parses the input just before packet number + * field. This function can handle Connection ID up to + * :macro:`NGTCP2_MAX_CIDLEN`. Consider to use + * `ngtcp2_pkt_decode_version_cid` to get longer Connection ID. It + * stores the result in the object pointed by |dest|, and returns the + * number of bytes decoded to read the packet header if it succeeds, + * or one of the following error codes: + * + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * Packet is too short; or it is not a short header + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, + const uint8_t *pkt, + size_t pktlen, + size_t dcidlen); + +/** + * @function + * + * `ngtcp2_pkt_write_stateless_reset` writes Stateless Reset packet in + * the buffer pointed by |dest| whose length is |destlen|. + * |stateless_reset_token| is a pointer to the Stateless Reset Token, + * and its length must be :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` + * bytes long. |rand| specifies the random octets preceding Stateless + * Reset Token. The length of |rand| is specified by |randlen| which + * must be at least :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN` bytes + * long. + * + * If |randlen| is too long to write them all in the buffer, |rand| is + * written to the buffer as much as possible, and is truncated. + * + * This function returns the number of bytes written to the buffer, or + * one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |randlen| is strictly less than + * :macro:`NGTCP2_MIN_STATELESS_RESET_RANDLEN`. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_stateless_reset( + uint8_t *dest, size_t destlen, const uint8_t *stateless_reset_token, + const uint8_t *rand, size_t randlen); + +/** + * @function + * + * `ngtcp2_pkt_write_version_negotiation` writes Version Negotiation + * packet in the buffer pointed by |dest| whose length is |destlen|. + * |unused_random| should be generated randomly. |dcid| is the + * destination connection ID which appears in a packet as a source + * connection ID sent by client which caused version negotiation. + * Similarly, |scid| is the source connection ID which appears in a + * packet as a destination connection ID sent by client. |sv| is a + * list of supported versions, and |nsv| specifies the number of + * supported versions included in |sv|. + * + * This function returns the number of bytes written to the buffer, or + * one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_version_negotiation( + uint8_t *dest, size_t destlen, uint8_t unused_random, const uint8_t *dcid, + size_t dcidlen, const uint8_t *scid, size_t scidlen, const uint32_t *sv, + size_t nsv); + +/** + * @struct + * + * :type:`ngtcp2_conn` represents a single QUIC connection. + */ +typedef struct ngtcp2_conn ngtcp2_conn; + +/** + * @functypedef + * + * :type:`ngtcp2_client_initial` is invoked when client application + * asks TLS stack to produce first TLS cryptographic handshake data. + * + * This implementation of this callback must get the first handshake + * data from TLS stack and pass it to ngtcp2 library using + * `ngtcp2_conn_submit_crypto_data` function. Make sure that before + * calling `ngtcp2_conn_submit_crypto_data` function, client + * application must create initial packet protection keys and IVs, and + * provide them to ngtcp2 library using + * `ngtcp2_conn_install_initial_key`. + * + * This callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_client_initial)(ngtcp2_conn *conn, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_client_initial` is invoked when server receives + * Initial packet from client. An server application must implement + * this callback, and generate initial keys and IVs for both + * transmission and reception. Install them using + * `ngtcp2_conn_install_initial_key`. |dcid| is the destination + * connection ID which client generated randomly. It is used to + * derive initial packet protection keys. + * + * The callback function must return 0 if it succeeds. If an error + * occurs, return :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the + * library call return immediately. + */ +typedef int (*ngtcp2_recv_client_initial)(ngtcp2_conn *conn, + const ngtcp2_cid *dcid, + void *user_data); + +/** + * @enum + * + * :type:`ngtcp2_crypto_level` is encryption level. + */ +typedef enum ngtcp2_crypto_level { + /** + * :enum:`NGTCP2_CRYPTO_LEVEL_INITIAL` is Initial Keys encryption + * level. + */ + NGTCP2_CRYPTO_LEVEL_INITIAL, + /** + * :enum:`NGTCP2_CRYPTO_LEVEL_HANDSHAKE` is Handshake Keys + * encryption level. + */ + NGTCP2_CRYPTO_LEVEL_HANDSHAKE, + /** + * :enum:`NGTCP2_CRYPTO_LEVEL_APPLICATION` is Application Data + * (1-RTT) Keys encryption level. + */ + NGTCP2_CRYPTO_LEVEL_APPLICATION, + /** + * :enum:`NGTCP2_CRYPTO_LEVEL_EARLY` is Early Data (0-RTT) Keys + * encryption level. + */ + NGTCP2_CRYPTO_LEVEL_EARLY +} ngtcp2_crypto_level; + +/** + * @functypedef + * + * :type`ngtcp2_recv_crypto_data` is invoked when crypto data is + * received. The received data is pointed to by |data|, and its + * length is |datalen|. The |offset| specifies the offset where + * |data| is positioned. |user_data| is the arbitrary pointer passed + * to `ngtcp2_conn_client_new` or `ngtcp2_conn_server_new`. The + * ngtcp2 library ensures that the crypto data is passed to the + * application in the increasing order of |offset|. |datalen| is + * always strictly greater than 0. |crypto_level| indicates the + * encryption level where this data is received. Crypto data can + * never be received in + * :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_EARLY`. + * + * The application should provide the given data to TLS stack. + * + * The callback function must return 0 if it succeeds, or one of the + * following negative error codes: + * + * - :macro:`NGTCP2_ERR_CRYPTO` + * - :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM` + * - :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` + * - :macro:`NGTCP2_ERR_TRANSPORT_PARAM` + * - :macro:`NGTCP2_ERR_PROTO` + * - :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE` + * - :macro:`NGTCP2_ERR_NOMEM` + * - :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * + * If the other value is returned, it is treated as + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE`. + * + * If application encounters fatal error, return + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_recv_crypto_data)(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, + size_t datalen, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_handshake_completed` is invoked when QUIC + * cryptographic handshake has completed. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_handshake_completed)(ngtcp2_conn *conn, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_handshake_confirmed` is invoked when QUIC + * cryptographic handshake is confirmed. The handshake confirmation + * means that both endpoints agree that handshake has finished. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_handshake_confirmed)(ngtcp2_conn *conn, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_version_negotiation` is invoked when Version + * Negotiation packet is received. |hd| is the pointer to the QUIC + * packet header object. The vector |sv| of |nsv| elements contains + * the QUIC version the server supports. Since Version Negotiation is + * only sent by server, this callback function is used by client only. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_recv_version_negotiation)(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_retry` is invoked when Retry packet is received. + * This callback is client use only. + * + * Application must regenerate packet protection key, IV, and header + * protection key for Initial packets using the destination connection + * ID obtained by :member:`hd->scid <ngtcp2_pkt_hd.scid>` and install + * them by calling `ngtcp2_conn_install_initial_key()`. + * + * 0-RTT data accepted by the ngtcp2 library will be automatically + * retransmitted as 0-RTT data by the library. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_recv_retry)(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_encrypt` is invoked when the ngtcp2 library asks the + * application to encrypt packet payload. The packet payload to + * encrypt is passed as |plaintext| of length |plaintextlen|. The + * AEAD cipher is |aead|. |aead_ctx| is the AEAD cipher context + * object which is initialized with encryption key. The nonce is + * passed as |nonce| of length |noncelen|. The Additional + * Authenticated Data is passed as |aad| of length |aadlen|. + * + * The implementation of this callback must encrypt |plaintext| using + * the negotiated cipher suite and write the ciphertext into the + * buffer pointed by |dest|. |dest| has enough capacity to store the + * ciphertext and any additional AEAD tag data. + * + * |dest| and |plaintext| may point to the same buffer. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_encrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @functypedef + * + * :type:`ngtcp2_decrypt` is invoked when the ngtcp2 library asks the + * application to decrypt packet payload. The packet payload to + * decrypt is passed as |ciphertext| of length |ciphertextlen|. The + * AEAD cipher is |aead|. |aead_ctx| is the AEAD cipher context + * object which is initialized with decryption key. The nonce is + * passed as |nonce| of length |noncelen|. The Additional + * Authenticated Data is passed as |aad| of length |aadlen|. + * + * The implementation of this callback must decrypt |ciphertext| using + * the negotiated cipher suite and write the ciphertext into the + * buffer pointed by |dest|. |dest| has enough capacity to store the + * cleartext. + * + * |dest| and |ciphertext| may point to the same buffer. + * + * The callback function must return 0 if it succeeds. If TLS stack + * fails to decrypt data, return :macro:`NGTCP2_ERR_DECRYPT`. For any + * other errors, return :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which + * makes the library call return immediately. + */ +typedef int (*ngtcp2_decrypt)(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen); + +/** + * @functypedef + * + * :type:`ngtcp2_hp_mask` is invoked when the ngtcp2 library asks the + * application to produce a mask to encrypt or decrypt packet header. + * The encryption cipher is |hp|. |hp_ctx| is the cipher context + * object which is initialized with header protection key. The sample + * is passed as |sample| which is :macro:`NGTCP2_HP_SAMPLELEN` bytes + * long. + * + * The implementation of this callback must produce a mask using the + * header protection cipher suite specified by QUIC specification and + * write the result into the buffer pointed by |dest|. The length of + * the mask must be at least :macro:`NGTCP2_HP_MASKLEN`. The library + * only uses the first :macro:`NGTCP2_HP_MASKLEN` bytes of the + * produced mask. The buffer pointed by |dest| is guaranteed to have + * at least :macro:`NGTCP2_HP_SAMPLELEN` bytes available for + * convenience. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library call + * return immediately. + */ +typedef int (*ngtcp2_hp_mask)(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample); + +/** + * @macrosection + * + * Stream data flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_DATA_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_STREAM_DATA_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_DATA_FLAG_FIN` indicates that this chunk of + * data is final piece of an incoming stream. + */ +#define NGTCP2_STREAM_DATA_FLAG_FIN 0x01u + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_DATA_FLAG_EARLY` indicates that this chunk of + * data contains data received in 0RTT packet and the handshake has + * not completed yet, which means that the data might be replayed. + */ +#define NGTCP2_STREAM_DATA_FLAG_EARLY 0x02u + +/** + * @functypedef + * + * :type:`ngtcp2_recv_stream_data` is invoked when stream data is + * received. The stream is specified by |stream_id|. |flags| is the + * bitwise-OR of zero or more of :macro:`NGTCP2_STREAM_DATA_FLAG_* + * <NGTCP2_STREAM_DATA_FLAG_NONE>`. If |flags| & + * :macro:`NGTCP2_STREAM_DATA_FLAG_FIN` is nonzero, this portion of + * the data is the last data in this stream. |offset| is the offset + * where this data begins. The library ensures that data is passed to + * the application in the non-decreasing order of |offset| without any + * overlap. The data is passed as |data| of length |datalen|. + * |datalen| may be 0 if and only if |fin| is nonzero. + * + * If :macro:`NGTCP2_STREAM_DATA_FLAG_EARLY` is set in |flags|, it + * indicates that a part of or whole data was received in 0RTT packet + * and a handshake has not completed yet. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return + * immediately. + */ +typedef int (*ngtcp2_recv_stream_data)(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, uint64_t offset, + const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_stream_open` is a callback function which is called + * when remote stream is opened by peer. This function is not called + * if stream is opened by implicitly (we might reconsider this + * behaviour). + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + */ +typedef int (*ngtcp2_stream_open)(ngtcp2_conn *conn, int64_t stream_id, + void *user_data); + +/** + * @macrosection + * + * Stream close flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_CLOSE_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_STREAM_CLOSE_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET` indicates that + * app_error_code parameter is set. + */ +#define NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET 0x01u + +/** + * @functypedef + * + * :type:`ngtcp2_stream_close` is invoked when a stream is closed. + * This callback is not called when QUIC connection is closed before + * existing streams are closed. |flags| is the bitwise-OR of zero or + * more of :macro:`NGTCP2_STREAM_CLOSE_FLAG_* + * <NGTCP2_STREAM_CLOSE_FLAG_NONE>`. |app_error_code| indicates the + * error code of this closure if + * :macro:`NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET` is set in + * |flags|. If it is not set, the stream was closed without any error + * code, which generally means success. + * + * |app_error_code| is the first application error code sent by a + * local endpoint, or received from a remote endpoint. If a stream is + * closed cleanly, no application error code is exchanged. Since QUIC + * stack does not know the application error code which indicates "no + * errors", |app_error_code| is set to 0 and + * :macro:`NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET` is not set in + * |flags| in this case. + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + */ +typedef int (*ngtcp2_stream_close)(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, uint64_t app_error_code, + void *user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_stream_reset` is invoked when a stream identified by + * |stream_id| is reset by a remote endpoint. + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + */ +typedef int (*ngtcp2_stream_reset)(ngtcp2_conn *conn, int64_t stream_id, + uint64_t final_size, uint64_t app_error_code, + void *user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_acked_stream_data_offset` is a callback function + * which is called when stream data is acked, and application can free + * the data. The acked range of data is [offset, offset + datalen). + * For a given stream_id, this callback is called sequentially in + * increasing order of |offset| without any overlap. |datalen| is + * normally strictly greater than 0. One exception is that when a + * packet which includes STREAM frame which has fin flag set, and 0 + * length data, this callback is invoked with 0 passed as |datalen|. + * + * If a stream is closed prematurely and stream data is still + * in-flight, this callback function is not called for those data. + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + */ +typedef int (*ngtcp2_acked_stream_data_offset)( + ngtcp2_conn *conn, int64_t stream_id, uint64_t offset, uint64_t datalen, + void *user_data, void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_stateless_reset` is a callback function which is + * called when Stateless Reset packet is received. The stateless + * reset details are given in |sr|. + * + * The implementation of this callback should return 0 if it succeeds. + * Returning :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library + * call return immediately. + */ +typedef int (*ngtcp2_recv_stateless_reset)(ngtcp2_conn *conn, + const ngtcp2_pkt_stateless_reset *sr, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_extend_max_streams` is a callback function which is + * called every time max stream ID is strictly extended. + * |max_streams| is the cumulative number of streams which an endpoint + * can open. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_extend_max_streams)(ngtcp2_conn *conn, + uint64_t max_streams, void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_extend_max_stream_data` is a callback function which + * is invoked when max stream data is extended. |stream_id| + * identifies the stream. |max_data| is a cumulative number of bytes + * the endpoint can send on this stream. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_extend_max_stream_data)(ngtcp2_conn *conn, + int64_t stream_id, + uint64_t max_data, void *user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_rand` is a callback function to get randomized byte + * string from application. Application must fill random |destlen| + * bytes to the buffer pointed by |dest|. The generated bytes are + * used only in non-cryptographic context. + */ +typedef void (*ngtcp2_rand)(uint8_t *dest, size_t destlen, + const ngtcp2_rand_ctx *rand_ctx); + +/** + * @functypedef + * + * :type:`ngtcp2_get_new_connection_id` is a callback function to ask + * an application for new connection ID. Application must generate + * new unused connection ID with the exact |cidlen| bytes and store it + * in |cid|. It also has to generate stateless reset token into + * |token|. The length of stateless reset token is + * :macro:`NGTCP2_STATELESS_RESET_TOKENLEN` and it is guaranteed that + * the buffer pointed by |cid| has the sufficient space to store the + * token. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_get_new_connection_id)(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_remove_connection_id` is a callback function which + * notifies the application that connection ID |cid| is no longer used + * by remote endpoint. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_remove_connection_id)(ngtcp2_conn *conn, + const ngtcp2_cid *cid, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_update_key` is a callback function which tells the + * application that it must generate new packet protection keying + * materials and AEAD cipher context objects with new keys. The + * current set of secrets are given as |current_rx_secret| and + * |current_tx_secret| of length |secretlen|. They are decryption and + * encryption secrets respectively. + * + * The application has to generate new secrets and keys for both + * encryption and decryption, and write decryption secret and IV to + * the buffer pointed by |rx_secret| and |rx_iv| respectively. It + * also has to create new AEAD cipher context object with new + * decryption key and initialize |rx_aead_ctx| with it. Similarly, + * write encryption secret and IV to the buffer pointed by |tx_secret| + * and |tx_iv|. Create new AEAD cipher context object with new + * encryption key and initialize |tx_aead_ctx| with it. All given + * buffers have the enough capacity to store secret, key and IV. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_update_key)( + ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, const uint8_t *current_tx_secret, + size_t secretlen, void *user_data); + +/** + * @macrosection + * + * Path validation related macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_PATH_VALIDATION_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_PATH_VALIDATION_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR` indicates the + * validation involving server preferred address. This flag is only + * set for client. + */ +#define NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR 0x01u + +/** + * @functypedef + * + * :type:`ngtcp2_path_validation` is a callback function which tells + * the application the outcome of path validation. |flags| is zero or + * more of :macro:`NGTCP2_PATH_VALIDATION_FLAG_* + * <NGTCP2_PATH_VALIDATION_FLAG_NONE>`. |path| is the path that was + * validated. If |res| is + * :enum:`ngtcp2_path_validation_result.NGTCP2_PATH_VALIDATION_RESULT_SUCCESS`, + * the path validation succeeded. If |res| is + * :enum:`ngtcp2_path_validation_result.NGTCP2_PATH_VALIDATION_RESULT_FAILURE`, + * the path validation failed. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_path_validation)(ngtcp2_conn *conn, uint32_t flags, + const ngtcp2_path *path, + ngtcp2_path_validation_result res, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_select_preferred_addr` is a callback function which + * asks a client application to choose server address from preferred + * addresses |paddr| received from server. An application should + * write a network path for a selected preferred address in |dest|. + * More specifically, the selected preferred address must be set to + * :member:`dest->remote <ngtcp2_path.remote>`, a client source + * address must be set to :member:`dest->local <ngtcp2_path.local>`. + * If a client source address does not change for the new server + * address, leave :member:`dest->local <ngtcp2_path.local>` + * unmodified, or copy the value of :member:`local + * <ngtcp2_path.local>` field of the current network path obtained + * from `ngtcp2_conn_get_path()`. Both :member:`dest->local.addr + * <ngtcp2_addr.addr>` and :member:`dest->remote.addr + * <ngtcp2_addr.addr>` point to buffers which are at least + * ``sizeof(struct sockaddr_storage)`` bytes long, respectively. If + * an application denies the preferred addresses, just leave |dest| + * unmodified (or set :member:`dest->remote.addrlen + * <ngtcp2_addr.addrlen>` to 0) and return 0. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_select_preferred_addr)(ngtcp2_conn *conn, + ngtcp2_path *dest, + const ngtcp2_preferred_addr *paddr, + void *user_data); + +/** + * @enum + * + * :type:`ngtcp2_connection_id_status_type` defines a set of status + * for Destination Connection ID. + */ +typedef enum ngtcp2_connection_id_status_type { + /** + * :enum:`NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE` indicates that + * a local endpoint starts using new destination Connection ID. + */ + NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE, + /** + * :enum:`NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE` indicates + * that a local endpoint stops using a given destination Connection + * ID. + */ + NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE +} ngtcp2_connection_id_status_type; + +/** + * @functypedef + * + * :type:`ngtcp2_connection_id_status` is a callback function which is + * called when the status of Connection ID changes. + * + * |token| is the associated stateless reset token and it is ``NULL`` + * if no token is present. + * + * |type| is the one of the value defined in + * :type:`ngtcp2_connection_id_status_type`. The new value might be + * added in the future release. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_connection_id_status)(ngtcp2_conn *conn, int type, + uint64_t seq, const ngtcp2_cid *cid, + const uint8_t *token, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_new_token` is a callback function which is + * called when new token is received from server. + * + * |token| is the received token. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_recv_new_token)(ngtcp2_conn *conn, const ngtcp2_vec *token, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_delete_crypto_aead_ctx` is a callback function which + * must delete the native object pointed by + * :member:`aead_ctx->native_handle + * <ngtcp2_crypto_aead_ctx.native_handle>`. + */ +typedef void (*ngtcp2_delete_crypto_aead_ctx)(ngtcp2_conn *conn, + ngtcp2_crypto_aead_ctx *aead_ctx, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_delete_crypto_cipher_ctx` is a callback function + * which must delete the native object pointed by + * :member:`cipher_ctx->native_handle + * <ngtcp2_crypto_cipher_ctx.native_handle>`. + */ +typedef void (*ngtcp2_delete_crypto_cipher_ctx)( + ngtcp2_conn *conn, ngtcp2_crypto_cipher_ctx *cipher_ctx, void *user_data); + +/** + * @macrosection + * + * Datagram flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_DATAGRAM_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_DATAGRAM_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_DATAGRAM_FLAG_EARLY` indicates that DATAGRAM frame + * is received in 0RTT packet and the handshake has not completed yet, + * which means that the data might be replayed. + */ +#define NGTCP2_DATAGRAM_FLAG_EARLY 0x01u + +/** + * @functypedef + * + * :type:`ngtcp2_recv_datagram` is invoked when DATAGRAM frame is + * received. |flags| is bitwise-OR of zero or more of + * :macro:`NGTCP2_DATAGRAM_FLAG_* <NGTCP2_DATAGRAM_FLAG_NONE>`. + * + * If :macro:`NGTCP2_DATAGRAM_FLAG_EARLY` is set in |flags|, it + * indicates that DATAGRAM frame was received in 0RTT packet and a + * handshake has not completed yet. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return + * immediately. + */ +typedef int (*ngtcp2_recv_datagram)(ngtcp2_conn *conn, uint32_t flags, + const uint8_t *data, size_t datalen, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_ack_datagram` is invoked when a packet which contains + * DATAGRAM frame which is identified by |dgram_id| is acknowledged. + * |dgram_id| is the valued passed to `ngtcp2_conn_writev_datagram`. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return + * immediately. + */ +typedef int (*ngtcp2_ack_datagram)(ngtcp2_conn *conn, uint64_t dgram_id, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_lost_datagram` is invoked when a packet which + * contains DATAGRAM frame which is identified by |dgram_id| is + * declared lost. |dgram_id| is the valued passed to + * `ngtcp2_conn_writev_datagram`. Note that the loss might be + * spurious, and DATAGRAM frame might be acknowledged later. + * + * The callback function must return 0 if it succeeds, or + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` which makes the library return + * immediately. + */ +typedef int (*ngtcp2_lost_datagram)(ngtcp2_conn *conn, uint64_t dgram_id, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_get_path_challenge_data` is a callback function to + * ask an application for new data that is sent in PATH_CHALLENGE + * frame. Application must generate new unpredictable exactly + * :macro:`NGTCP2_PATH_CHALLENGE_DATALEN` bytes of random data and + * store them into the buffer pointed by |data|. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_get_path_challenge_data)(ngtcp2_conn *conn, uint8_t *data, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_stream_stop_sending` is invoked when a stream is no + * longer read by a local endpoint before it receives all stream data. + * This function is called at most once per stream. |app_error_code| + * is the error code passed to `ngtcp2_conn_shutdown_stream_read` or + * `ngtcp2_conn_shutdown_stream`. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_stream_stop_sending)(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, + void *user_data, + void *stream_user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_version_negotiation` is invoked when the compatible + * version negotiation takes place. For client, it is called when it + * sees a change in version field of a long header packet. This + * callback function might be called multiple times for client. For + * server, it is called once when the version is negotiated. + * + * The implementation of this callback must install new Initial keys + * for |version|. Use `ngtcp2_conn_install_vneg_initial_key` to + * install keys. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_version_negotiation)(ngtcp2_conn *conn, uint32_t version, + const ngtcp2_cid *client_dcid, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_recv_key` is invoked when new key is installed to + * |conn| during QUIC cryptographic handshake. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_recv_key)(ngtcp2_conn *conn, ngtcp2_crypto_level level, + void *user_data); + +/** + * @functypedef + * + * :type:`ngtcp2_early_data_rejected` is invoked when early data was + * rejected by server, or client decided not to attempt early data. + * + * The callback function must return 0 if it succeeds. Returning + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_early_data_rejected)(ngtcp2_conn *conn, void *user_data); + +#define NGTCP2_CALLBACKS_VERSION_V1 1 +#define NGTCP2_CALLBACKS_VERSION NGTCP2_CALLBACKS_VERSION_V1 + +/** + * @struct + * + * :type:`ngtcp2_callbacks` holds a set of callback functions. + */ +typedef struct ngtcp2_callbacks { + /** + * :member:`client_initial` is a callback function which is invoked + * when client asks TLS stack to produce first TLS cryptographic + * handshake message. This callback function must be specified for + * a client application. + */ + ngtcp2_client_initial client_initial; + /** + * :member:`recv_client_initial` is a callback function which is + * invoked when a server receives the first packet from client. + * This callback function must be specified for a server application. + */ + ngtcp2_recv_client_initial recv_client_initial; + /** + * :member:`recv_crypto_data` is a callback function which is + * invoked when cryptographic data (CRYPTO frame, in other words, + * TLS message) is received. This callback function must be + * specified. + */ + ngtcp2_recv_crypto_data recv_crypto_data; + /** + * :member:`handshake_completed` is a callback function which is + * invoked when QUIC cryptographic handshake has completed. This + * callback function is optional. + */ + ngtcp2_handshake_completed handshake_completed; + /** + * :member:`recv_version_negotiation` is a callback function which + * is invoked when Version Negotiation packet is received by a + * client. This callback function is optional. + */ + ngtcp2_recv_version_negotiation recv_version_negotiation; + /** + * :member:`encrypt` is a callback function which is invoked to + * encrypt a QUIC packet. This callback function must be specified. + */ + ngtcp2_encrypt encrypt; + /** + * :member:`decrypt` is a callback function which is invoked to + * decrypt a QUIC packet. This callback function must be specified. + */ + ngtcp2_decrypt decrypt; + /** + * :member:`hp_mask` is a callback function which is invoked to get + * a mask to encrypt or decrypt packet header. This callback + * function must be specified. + */ + ngtcp2_hp_mask hp_mask; + /** + * :member:`recv_stream_data` is a callback function which is + * invoked when STREAM data, which includes application data, is + * received. This callback function is optional. + */ + ngtcp2_recv_stream_data recv_stream_data; + /** + * :member:`acked_stream_data_offset` is a callback function which + * is invoked when STREAM data, which includes application data, is + * acknowledged by a remote endpoint. It tells an application the + * largest offset of acknowledged STREAM data without a gap so that + * application can free memory for the data. This callback function + * is optional. + */ + ngtcp2_acked_stream_data_offset acked_stream_data_offset; + /** + * :member:`stream_open` is a callback function which is invoked + * when new remote stream is opened by a remote endpoint. This + * callback function is optional. + */ + ngtcp2_stream_open stream_open; + /** + * :member:`stream_close` is a callback function which is invoked + * when a stream is closed. This callback function is optional. + */ + ngtcp2_stream_close stream_close; + /** + * :member:`recv_stateless_reset` is a callback function which is + * invoked when Stateless Reset packet is received. This callback + * function is optional. + */ + ngtcp2_recv_stateless_reset recv_stateless_reset; + /** + * :member:`recv_retry` is a callback function which is invoked when + * a client receives Retry packet. For client, this callback + * function must be specified. Server never receive Retry packet. + */ + ngtcp2_recv_retry recv_retry; + /** + * :member:`extend_max_local_streams_bidi` is a callback function + * which is invoked when the number of bidirectional stream which a + * local endpoint can open is increased. This callback function is + * optional. + */ + ngtcp2_extend_max_streams extend_max_local_streams_bidi; + /** + * :member:`extend_max_local_streams_uni` is a callback function + * which is invoked when the number of unidirectional stream which a + * local endpoint can open is increased. This callback function is + * optional. + */ + ngtcp2_extend_max_streams extend_max_local_streams_uni; + /** + * :member:`rand` is a callback function which is invoked when the + * library needs sequence of random data. This callback function + * must be specified. + */ + ngtcp2_rand rand; + /** + * :member:`get_new_connection_id` is a callback function which is + * invoked when the library needs new connection ID. This callback + * function must be specified. + */ + ngtcp2_get_new_connection_id get_new_connection_id; + /** + * :member:`remove_connection_id` is a callback function which + * notifies an application that connection ID is no longer used by a + * remote endpoint. This callback function is optional. + */ + ngtcp2_remove_connection_id remove_connection_id; + /** + * :member:`update_key` is a callback function which is invoked when + * the library tells an application that it must update keying + * materials and install new keys. This callback function must be + * specified. + */ + ngtcp2_update_key update_key; + /** + * :member:`path_validation` is a callback function which is invoked + * when path validation completed. This callback function is + * optional. + */ + ngtcp2_path_validation path_validation; + /** + * :member:`select_preferred_addr` is a callback function which is + * invoked when the library asks a client to select preferred + * address presented by a server. This callback function is + * optional. + */ + ngtcp2_select_preferred_addr select_preferred_addr; + /** + * :member:`stream_reset` is a callback function which is invoked + * when a stream is reset by a remote endpoint. This callback + * function is optional. + */ + ngtcp2_stream_reset stream_reset; + /** + * :member:`extend_max_remote_streams_bidi` is a callback function + * which is invoked when the number of bidirectional streams which a + * remote endpoint can open is increased. This callback function is + * optional. + */ + ngtcp2_extend_max_streams extend_max_remote_streams_bidi; + /** + * :member:`extend_max_remote_streams_uni` is a callback function + * which is invoked when the number of unidirectional streams which + * a remote endpoint can open is increased. This callback function + * is optional. + */ + ngtcp2_extend_max_streams extend_max_remote_streams_uni; + /** + * :member:`extend_max_stream_data` is callback function which is + * invoked when the maximum offset of STREAM data that a local + * endpoint can send is increased. This callback function is + * optional. + */ + ngtcp2_extend_max_stream_data extend_max_stream_data; + /** + * :member:`dcid_status` is a callback function which is invoked + * when the new destination Connection ID is activated or the + * activated destination Connection ID is now deactivated. This + * callback function is optional. + */ + ngtcp2_connection_id_status dcid_status; + /** + * :member:`handshake_confirmed` is a callback function which is + * invoked when both endpoints agree that handshake has finished. + * This field is ignored by server because handshake_completed + * indicates the handshake confirmation for server. This callback + * function is optional. + */ + ngtcp2_handshake_confirmed handshake_confirmed; + /** + * :member:`recv_new_token` is a callback function which is invoked + * when new token is received from server. This field is ignored by + * server. This callback function is optional. + */ + ngtcp2_recv_new_token recv_new_token; + /** + * :member:`delete_crypto_aead_ctx` is a callback function which + * deletes a given AEAD cipher context object. This callback + * function must be specified. + */ + ngtcp2_delete_crypto_aead_ctx delete_crypto_aead_ctx; + /** + * :member:`delete_crypto_cipher_ctx` is a callback function which + * deletes a given cipher context object. This callback function + * must be specified. + */ + ngtcp2_delete_crypto_cipher_ctx delete_crypto_cipher_ctx; + /** + * :member:`recv_datagram` is a callback function which is invoked + * when DATAGRAM frame is received. This callback function is + * optional. + */ + ngtcp2_recv_datagram recv_datagram; + /** + * :member:`ack_datagram` is a callback function which is invoked + * when a packet containing DATAGRAM frame is acknowledged. This + * callback function is optional. + */ + ngtcp2_ack_datagram ack_datagram; + /** + * :member:`lost_datagram` is a callback function which is invoked + * when a packet containing DATAGRAM frame is declared lost. This + * callback function is optional. + */ + ngtcp2_lost_datagram lost_datagram; + /** + * :member:`get_path_challenge_data` is a callback function which is + * invoked when the library needs new PATH_CHALLENGE data. This + * callback must be specified. + */ + ngtcp2_get_path_challenge_data get_path_challenge_data; + /** + * :member:`stream_stop_sending` is a callback function which is + * invoked when a local endpoint no longer reads from a stream + * before it receives all stream data. This callback function is + * optional. + */ + ngtcp2_stream_stop_sending stream_stop_sending; + /** + * :member:`version_negotiation` is a callback function which is + * invoked when the compatible version negotiation takes place. + * This callback function must be specified. + */ + ngtcp2_version_negotiation version_negotiation; + /** + * :member:`recv_rx_key` is a callback function which is invoked + * when a new key for decrypting packets is installed during QUIC + * cryptographic handshake. It is not called for + * :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_INITIAL`. + */ + ngtcp2_recv_key recv_rx_key; + /** + * :member:`recv_tx_key` is a callback function which is invoked + * when a new key for encrypting packets is installed during QUIC + * cryptographic handshake. It is not called for + * :enum:`ngtcp2_crypto_level.NGTCP2_CRYPTO_LEVEL_INITIAL`. + */ + ngtcp2_recv_key recv_tx_key; + /** + * :member:`ngtcp2_early_data_rejected` is a callback function which + * is invoked when an attempt to send early data by client was + * rejected by server, or client decided not to attempt early data. + * This callback function is only used by client. + */ + ngtcp2_early_data_rejected early_data_rejected; +} ngtcp2_callbacks; + +/** + * @function + * + * `ngtcp2_pkt_write_connection_close` writes Initial packet + * containing CONNECTION_CLOSE frame with the given |error_code| and + * the optional |reason| of length |reasonlen| to the buffer pointed + * by |dest| of length |destlen|. All encryption parameters are for + * Initial packet encryption. The packet number is always 0. + * + * The primary use case of this function is for server to send + * CONNECTION_CLOSE frame in Initial packet to close connection + * without committing the state when validating Retry token fails. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * Callback function failed. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_connection_close( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, uint64_t error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, + ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_pkt_write_retry` writes Retry packet in the buffer pointed + * by |dest| whose length is |destlen|. |dcid| is the destination + * connection ID which appeared in a packet as a source connection ID + * sent by client. |scid| is a server chosen source connection ID. + * |odcid| specifies Original Destination Connection ID which appeared + * in a packet as a destination connection ID sent by client. |token| + * specifies Retry Token, and |tokenlen| specifies its length. |aead| + * must be AEAD_AES_128_GCM. |aead_ctx| must be initialized with + * :macro:`NGTCP2_RETRY_KEY` as an encryption key. + * + * This function returns the number of bytes written to the buffer, or + * one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * Callback function failed. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * :member:`odcid->datalen <ngtcp2_cid.datalen>` is less than + * :macro:`NGTCP2_MIN_INITIAL_DCIDLEN`. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_pkt_write_retry( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token, + size_t tokenlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx); + +/** + * @function + * + * `ngtcp2_accept` is used by server implementation, and decides + * whether packet |pkt| of length |pktlen| from client is acceptable + * for the very initial packet to a connection. + * + * If |dest| is not ``NULL`` and the function returns 0, or + * :macro:`NGTCP2_ERR_RETRY`, the decoded packet header is stored to + * the object pointed by |dest|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_RETRY` + * Retry packet should be sent. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * The packet is not acceptable for the very first packet to a new + * connection; or the function failed to parse the packet header. + */ +NGTCP2_EXTERN int ngtcp2_accept(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen); + +/** + * @function + * + * `ngtcp2_conn_client_new` creates new :type:`ngtcp2_conn`, and + * initializes it as client. |dcid| is randomized destination + * connection ID. |scid| is source connection ID. + * |client_chosen_version| is a QUIC version that a client chooses. + * |path| is the network path where this QUIC connection is being + * established and must not be ``NULL``. |callbacks|, |settings|, and + * |params| must not be ``NULL``, and the function make a copy of each + * of them. |params| is local QUIC transport parameters and sent to a + * remote endpoint during handshake. |user_data| is the arbitrary + * pointer which is passed to the user-defined callback functions. If + * |mem| is ``NULL``, the memory allocator returned by + * `ngtcp2_mem_default()` is used. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_client_new_versioned( + ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_path *path, uint32_t client_chosen_version, + int callbacks_version, const ngtcp2_callbacks *callbacks, + int settings_version, const ngtcp2_settings *settings, + int transport_params_version, const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data); + +/** + * @function + * + * `ngtcp2_conn_server_new` creates new :type:`ngtcp2_conn`, and + * initializes it as server. |dcid| is a destination connection ID. + * |scid| is a source connection ID. |path| is the network path where + * this QUIC connection is being established and must not be ``NULL``. + * |client_chosen_version| is a QUIC version that a client chooses. + * |callbacks|, |settings|, and |params| must not be ``NULL``, and the + * function make a copy of each of them. |params| is local QUIC + * transport parameters and sent to a remote endpoint during + * handshake. |user_data| is the arbitrary pointer which is passed to + * the user-defined callback functions. If |mem| is ``NULL``, the + * memory allocator returned by `ngtcp2_mem_default()` is used. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_server_new_versioned( + ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_path *path, uint32_t client_chosen_version, + int callbacks_version, const ngtcp2_callbacks *callbacks, + int settings_version, const ngtcp2_settings *settings, + int transport_params_version, const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data); + +/** + * @function + * + * `ngtcp2_conn_del` frees resources allocated for |conn|. It also + * frees memory pointed by |conn|. + */ +NGTCP2_EXTERN void ngtcp2_conn_del(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_read_pkt` decrypts QUIC packet given in |pkt| of + * length |pktlen| and processes it. |path| is the network path the + * packet is delivered and must not be ``NULL``. |pi| is packet + * metadata and may be ``NULL``. This function performs QUIC handshake + * as well. + * + * This function must not be called from inside the callback + * functions. + * + * This function returns 0 if it succeeds, or negative error codes. + * If :macro:`NGTCP2_ERR_RETRY` is returned, application must be a + * server and it must perform address validation by sending Retry + * packet and discard the connection state. If + * :macro:`NGTCP2_ERR_DROP_CONN` is returned, server application must + * drop the connection silently (without sending any CONNECTION_CLOSE + * frame) and discard connection state. If + * :macro:`NGTCP2_ERR_DRAINING` is returned, a connection has entered + * the draining state, and no further packet transmission is allowed. + * If :macro:`NGTCP2_ERR_CRYPTO` is returned, the error happened in + * TLS stack and `ngtcp2_conn_get_tls_alert` returns TLS alert if set. + * + * If any other negative errors are returned, call + * `ngtcp2_conn_write_connection_close` to get terminal packet, and + * sending it makes QUIC connection enter the closing state. + */ +NGTCP2_EXTERN int +ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path, + int pkt_info_version, const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_write_pkt` is equivalent to calling + * `ngtcp2_conn_writev_stream` with -1 as stream_id, no stream data, and + * :macro:`NGTCP2_WRITE_STREAM_FLAG_NONE` as flags. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_pkt_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_handshake_completed` tells |conn| that the TLS stack + * declares TLS handshake completion. This does not mean QUIC + * handshake has completed. The library needs extra conditions to be + * met. + */ +NGTCP2_EXTERN void ngtcp2_conn_handshake_completed(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_handshake_completed` returns nonzero if QUIC handshake + * has completed. + */ +NGTCP2_EXTERN int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_install_initial_key` installs packet protection keying + * materials for Initial packets. |rx_aead_ctx| is AEAD cipher + * context object and must be initialized with a decryption key. + * |rx_iv| is IV of length |rx_ivlen| for decryption. |rx_hp_ctx| is + * a packet header protection cipher context object for decryption. + * Similarly, |tx_aead_ctx|, |tx_iv| and |tx_hp_ctx| are for + * encrypting outgoing packets and are the same length with the + * decryption counterpart . If they have already been set, they are + * overwritten. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |rx_aead_ctx|, + * |rx_hp_ctx|, |tx_aead_ctx|, and |tx_hp_ctx|. + * :type:`ngtcp2_delete_crypto_aead_ctx` and + * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete + * these objects when they are no longer used. If this function + * fails, the caller is responsible to delete them. + * + * After receiving Retry packet, the DCID most likely changes. In + * that case, client application must generate these keying materials + * again based on new DCID and install them again. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_initial_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *rx_aead_ctx, + const uint8_t *rx_iv, const ngtcp2_crypto_cipher_ctx *rx_hp_ctx, + const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv, + const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen); + +/** + * @function + * + * `ngtcp2_conn_install_vneg_initial_key` installs packet protection + * keying materials for Initial packets on compatible version + * negotiation for |version|. |rx_aead_ctx| is AEAD cipher context + * object and must be initialized with a decryption key. |rx_iv| is + * IV of length |rx_ivlen| for decryption. |rx_hp_ctx| is a packet + * header protection cipher context object for decryption. Similarly, + * |tx_aead_ctx|, |tx_iv| and |tx_hp_ctx| are for encrypting outgoing + * packets and are the same length with the decryption counterpart . + * If they have already been set, they are overwritten. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |rx_aead_ctx|, + * |rx_hp_ctx|, |tx_aead_ctx|, and |tx_hp_ctx|. + * :type:`ngtcp2_delete_crypto_aead_ctx` and + * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete + * these objects when they are no longer used. If this function + * fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_vneg_initial_key( + ngtcp2_conn *conn, uint32_t version, + const ngtcp2_crypto_aead_ctx *rx_aead_ctx, const uint8_t *rx_iv, + const ngtcp2_crypto_cipher_ctx *rx_hp_ctx, + const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv, + const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen); + +/** + * @function + * + * `ngtcp2_conn_install_rx_handshake_key` installs packet protection + * keying materials for decrypting incoming Handshake packets. + * |aead_ctx| is AEAD cipher context object which must be initialized + * with a decryption key. |iv| is IV of length |ivlen|. |hp_ctx| is + * a packet header protection cipher context object. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx|, + * and |hp_ctx|. :type:`ngtcp2_delete_crypto_aead_ctx` and + * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete + * these objects when they are no longer used. If this function + * fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_rx_handshake_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_install_tx_handshake_key` installs packet protection + * keying materials for encrypting outgoing Handshake packets. + * |aead_ctx| is AEAD cipher context object which must be initialized + * with an encryption key. |iv| is IV of length |ivlen|. |hp_ctx| is + * a packet header protection cipher context object. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx| and + * |hp_ctx|. :type:`ngtcp2_delete_crypto_aead_ctx` and + * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete + * these objects when they are no longer used. If this function + * fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_tx_handshake_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_install_early_key` installs packet protection AEAD + * cipher context object |aead_ctx|, IV |iv| of length |ivlen|, and + * packet header protection cipher context object |hp_ctx| to encrypt + * (for client) or decrypt (for server) 0RTT packets. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx| and + * |hp_ctx|. :type:`ngtcp2_delete_crypto_aead_ctx` and + * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete + * these objects when they are no longer used. If this function + * fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_early_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_install_rx_key` installs packet protection keying + * materials for decrypting Short header packets. |secret| of length + * |secretlen| is the decryption secret which is used to derive keying + * materials passed to this function. |aead_ctx| is AEAD cipher + * context object which must be initialized with a decryption key. + * |iv| is IV of length |ivlen|. |hp_ctx| is a packet header + * protection cipher context object. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx| and + * |hp_ctx|. :type:`ngtcp2_delete_crypto_aead_ctx` and + * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete + * these objects when they are no longer used. If this function + * fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_rx_key( + ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_install_tx_key` installs packet protection keying + * materials for encrypting Short header packets. |secret| of length + * |secretlen| is the encryption secret which is used to derive keying + * materials passed to this function. |aead_ctx| is AEAD cipher + * context object which must be initialized with an encryption key. + * |iv| is IV of length |ivlen|. |hp_ctx| is a packet header + * protection cipher context object. + * + * |ivlen| must be the minimum length of AEAD nonce, or 8 bytes if + * that is larger. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx| and + * |hp_ctx|. :type:`ngtcp2_delete_crypto_aead_ctx` and + * :type:`ngtcp2_delete_crypto_cipher_ctx` will be called to delete + * these objects when they are no longer used. If this function + * fails, the caller is responsible to delete them. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_install_tx_key( + ngtcp2_conn *conn, const uint8_t *secret, size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx); + +/** + * @function + * + * `ngtcp2_conn_initiate_key_update` initiates the key update. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_INVALID_STATE` + * The previous key update has not been confirmed yet; or key + * update is too frequent; or new keys are not available yet. + */ +NGTCP2_EXTERN int ngtcp2_conn_initiate_key_update(ngtcp2_conn *conn, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_set_tls_error` sets the TLS related error |liberr| in + * |conn|. |liberr| must be one of ngtcp2 library error codes (which + * is defined as NGTCP2_ERR_* macro, such as + * :macro:`NGTCP2_ERR_DECRYPT`). In general, error code should be + * propagated via return value, but sometimes ngtcp2 API is called + * inside callback function of TLS stack and it does not allow to + * return ngtcp2 error code directly. In this case, implementation + * can set the error code (e.g., + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM`) using this function. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_tls_error(ngtcp2_conn *conn, int liberr); + +/** + * @function + * + * `ngtcp2_conn_get_tls_error` returns the value set by + * `ngtcp2_conn_set_tls_error`. If no value is set, this function + * returns 0. + */ +NGTCP2_EXTERN int ngtcp2_conn_get_tls_error(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_tls_alert` sets a TLS alert |alert| generated by a + * local endpoint to |conn|. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_tls_alert(ngtcp2_conn *conn, uint8_t alert); + +/** + * @function + * + * `ngtcp2_conn_get_tls_alert` returns the value set by + * `ngtcp2_conn_set_tls_alert`. If no value is set, this function + * returns 0. + */ +NGTCP2_EXTERN uint8_t ngtcp2_conn_get_tls_alert(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_keep_alive_timeout` sets keep-alive timeout. If + * nonzero value is given, after a connection is idle at least in a + * given amount of time, a keep-alive packet is sent. If 0 is set, + * keep-alive functionality is disabled and this is the default. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_keep_alive_timeout(ngtcp2_conn *conn, + ngtcp2_duration timeout); + +/** + * @function + * + * `ngtcp2_conn_get_expiry` returns the next expiry time. It returns + * ``UINT64_MAX`` if there is no next expiry. + * + * Call `ngtcp2_conn_handle_expiry()` and `ngtcp2_conn_write_pkt` (or + * `ngtcp2_conn_writev_stream`) if expiry time is passed. + */ +NGTCP2_EXTERN ngtcp2_tstamp ngtcp2_conn_get_expiry(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_handle_expiry` handles expired timer. It does nothing + * if timer is not expired. + */ +NGTCP2_EXTERN int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_get_pto` returns Probe Timeout (PTO). + */ +NGTCP2_EXTERN ngtcp2_duration ngtcp2_conn_get_pto(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_decode_remote_transport_params` decodes QUIC transport + * parameters from the buffer pointed by |data| of length |datalen|, + * and sets the result to |conn|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM` + * The required parameter is missing. + * :macro:`NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM` + * The input is malformed. + * :macro:`NGTCP2_ERR_TRANSPORT_PARAM` + * Failed to validate the remote QUIC transport parameters. + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE` + * Version negotiation failure. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +NGTCP2_EXTERN int +ngtcp2_conn_decode_remote_transport_params(ngtcp2_conn *conn, + const uint8_t *data, size_t datalen); + +/** + * @function + * + * `ngtcp2_conn_get_remote_transport_params` returns a pointer to the + * remote QUIC transport parameters. If no remote transport + * parameters are set, it returns NULL. + */ +NGTCP2_EXTERN const ngtcp2_transport_params * +ngtcp2_conn_get_remote_transport_params(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_early_remote_transport_params` sets |params| as + * transport parameters previously received from a server. The + * parameters are used to send early data. QUIC requires that client + * application should remember transport parameters along with a + * session ticket. + * + * At least following fields should be set: + * + * - initial_max_stream_id_bidi + * - initial_max_stream_id_uni + * - initial_max_stream_data_bidi_local + * - initial_max_stream_data_bidi_remote + * - initial_max_stream_data_uni + * - initial_max_data + * - active_connection_id_limit + * - max_datagram_frame_size (if DATAGRAM extension was negotiated) + * + * The following fields are ignored: + * + * - ack_delay_exponent + * - max_ack_delay + * - initial_scid + * - original_dcid + * - preferred_address and preferred_address_present + * - retry_scid and retry_scid_present + * - stateless_reset_token and stateless_reset_token_present + */ +NGTCP2_EXTERN void ngtcp2_conn_set_early_remote_transport_params_versioned( + ngtcp2_conn *conn, int transport_params_version, + const ngtcp2_transport_params *params); + +/** + * @function + * + * `ngtcp2_conn_set_local_transport_params` sets the local transport + * parameters |params|. This function can only be called by server. + * Although the local transport parameters are passed to + * `ngtcp2_conn_server_new`, server might want to update them after + * ALPN is chosen. In that case, server can update the transport + * parameter with this function. Server must call this function + * before calling `ngtcp2_conn_install_tx_handshake_key`. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_INVALID_STATE` + * `ngtcp2_conn_install_tx_handshake_key` has been called. + */ +NGTCP2_EXTERN int ngtcp2_conn_set_local_transport_params_versioned( + ngtcp2_conn *conn, int transport_params_version, + const ngtcp2_transport_params *params); + +/** + * @function + * + * `ngtcp2_conn_get_local_transport_params` returns a pointer to the + * local QUIC transport parameters. + */ +NGTCP2_EXTERN const ngtcp2_transport_params * +ngtcp2_conn_get_local_transport_params(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_encode_local_transport_params` encodes the local QUIC + * transport parameters in |dest| of length |destlen|. This is + * equivalent to calling `ngtcp2_conn_get_local_transport_params` and + * then `ngtcp2_encode_transport_params`. + * + * This function returns the number of written, or one of the + * following negative error codes: + * + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_encode_local_transport_params( + ngtcp2_conn *conn, uint8_t *dest, size_t destlen); + +/** + * @function + * + * `ngtcp2_conn_open_bidi_stream` opens new bidirectional stream. The + * |stream_user_data| is the user data specific to the stream. The + * open stream ID is stored in |*pstream_id|. + * + * Application can call this function before handshake completes. For + * 0RTT packet, application can call this function after calling + * `ngtcp2_conn_set_early_remote_transport_params`. For 1RTT packet, + * application can call this function after calling + * `ngtcp2_conn_decode_remote_transport_params` and + * `ngtcp2_conn_install_tx_key`. If ngtcp2 crypto support library is + * used, application can call this function after calling + * `ngtcp2_crypto_derive_and_install_tx_key` for 1RTT packet. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` + * The remote peer does not allow |stream_id| yet. + */ +NGTCP2_EXTERN int ngtcp2_conn_open_bidi_stream(ngtcp2_conn *conn, + int64_t *pstream_id, + void *stream_user_data); + +/** + * @function + * + * `ngtcp2_conn_open_uni_stream` opens new unidirectional stream. The + * |stream_user_data| is the user data specific to the stream. The + * open stream ID is stored in |*pstream_id|. + * + * Application can call this function before handshake completes. For + * 0RTT packet, application can call this function after calling + * `ngtcp2_conn_set_early_remote_transport_params`. For 1RTT packet, + * application can call this function after calling + * `ngtcp2_conn_decode_remote_transport_params` and + * `ngtcp2_conn_install_tx_key`. If ngtcp2 crypto support library is + * used, application can call this function after calling + * `ngtcp2_crypto_derive_and_install_tx_key` for 1RTT packet. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_STREAM_ID_BLOCKED` + * The remote peer does not allow |stream_id| yet. + */ +NGTCP2_EXTERN int ngtcp2_conn_open_uni_stream(ngtcp2_conn *conn, + int64_t *pstream_id, + void *stream_user_data); + +/** + * @function + * + * `ngtcp2_conn_shutdown_stream` closes stream denoted by |stream_id| + * abruptly. |app_error_code| is one of application error codes, and + * indicates the reason of shutdown. Successful call of this function + * does not immediately erase the state of the stream. The actual + * deletion is done when the remote endpoint sends acknowledgement. + * Calling this function is equivalent to call + * `ngtcp2_conn_shutdown_stream_read`, and + * `ngtcp2_conn_shutdown_stream_write` sequentially with the following + * differences. If |stream_id| refers to a local unidirectional + * stream, this function only shutdowns write side of the stream. If + * |stream_id| refers to a remote unidirectional stream, this function + * only shutdowns read side of the stream. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + */ +NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream(ngtcp2_conn *conn, + int64_t stream_id, + uint64_t app_error_code); + +/** + * @function + * + * `ngtcp2_conn_shutdown_stream_write` closes write-side of stream + * denoted by |stream_id| abruptly. |app_error_code| is one of + * application error codes, and indicates the reason of shutdown. If + * this function succeeds, no application data is sent to the remote + * endpoint. It discards all data which has not been acknowledged + * yet. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |stream_id| refers to a remote unidirectional stream. + */ +NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_write(ngtcp2_conn *conn, + int64_t stream_id, + uint64_t app_error_code); + +/** + * @function + * + * `ngtcp2_conn_shutdown_stream_read` closes read-side of stream + * denoted by |stream_id| abruptly. |app_error_code| is one of + * application error codes, and indicates the reason of shutdown. If + * this function succeeds, no application data is forwarded to an + * application layer. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |stream_id| refers to a local unidirectional stream. + */ +NGTCP2_EXTERN int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, + int64_t stream_id, + uint64_t app_error_code); + +/** + * @macrosection + * + * Write stream data flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_WRITE_STREAM_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_WRITE_STREAM_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` indicates that more data may + * come and should be coalesced into the same packet if possible. + */ +#define NGTCP2_WRITE_STREAM_FLAG_MORE 0x01u + +/** + * @macro + * + * :macro:`NGTCP2_WRITE_STREAM_FLAG_FIN` indicates that the passed + * data is the final part of a stream. + */ +#define NGTCP2_WRITE_STREAM_FLAG_FIN 0x02u + +/** + * @function + * + * `ngtcp2_conn_write_stream` is just like + * `ngtcp2_conn_writev_stream`. The only difference is that it + * conveniently accepts a single buffer. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_stream_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen, + uint32_t flags, int64_t stream_id, const uint8_t *data, size_t datalen, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_writev_stream` writes a packet containing stream data + * of stream denoted by |stream_id|. The buffer of the packet is + * pointed by |dest| of length |destlen|. This function performs QUIC + * handshake as well. + * + * |destlen| should be at least + * :member:`ngtcp2_settings.max_tx_udp_payload_size`. + * + * Specifying -1 to |stream_id| means no new stream data to send. + * + * If |path| is not ``NULL``, this function stores the network path + * with which the packet should be sent. Each addr field must point + * to the buffer which should be at least ``sizeof(struct + * sockaddr_storage)`` bytes long. The assignment might not be done + * if nothing is written to |dest|. + * + * If |pi| is not ``NULL``, this function stores packet metadata in it + * if it succeeds. The metadata includes ECN markings. When calling + * this function again after it returns + * :macro:`NGTCP2_ERR_WRITE_MORE`, caller must pass the same |pi| to + * this function. + * + * If the all given data is encoded as STREAM frame in |dest|, and if + * |flags| & :macro:`NGTCP2_WRITE_STREAM_FLAG_FIN` is nonzero, fin + * flag is set to outgoing STREAM frame. Otherwise, fin flag in + * STREAM frame is not set. + * + * This packet may contain frames other than STREAM frame. The packet + * might not contain STREAM frame if other frames occupy the packet. + * In that case, |*pdatalen| would be -1 if |pdatalen| is not + * ``NULL``. + * + * If |flags| & :macro:`NGTCP2_WRITE_STREAM_FLAG_FIN` is nonzero, and + * 0 length STREAM frame is successfully serialized, |*pdatalen| would + * be 0. + * + * The number of data encoded in STREAM frame is stored in |*pdatalen| + * if it is not ``NULL``. The caller must keep the portion of data + * covered by |*pdatalen| bytes in tact until + * :type:`ngtcp2_acked_stream_data_offset` indicates that they are + * acknowledged by a remote endpoint or the stream is closed. + * + * If |flags| equals to :macro:`NGTCP2_WRITE_STREAM_FLAG_NONE`, this + * function produces a single payload of UDP packet. If the given + * stream data is small (e.g., few bytes), the packet might be + * severely under filled. Too many small packet might increase + * overall packet processing costs. Unless there are retransmissions, + * by default, application can only send 1 STREAM frame in one QUIC + * packet. In order to include more than 1 STREAM frame in one QUIC + * packet, specify :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` in |flags|. + * This is analogous to ``MSG_MORE`` flag in :manpage:`send(2)`. If + * the :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` is used, there are 4 + * outcomes: + * + * - The function returns the written length of packet just like + * without :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE`. This is because + * packet is nearly full and the library decided to make a complete + * packet. |*pdatalen| might be -1 or >= 0. It may return 0 which + * indicates that no packet transmission is possible at the moment + * for some reason. + * + * - The function returns :macro:`NGTCP2_ERR_WRITE_MORE`. In this + * case, |*pdatalen| >= 0 is asserted. It indicates that + * application can still call this function with different stream + * data (or `ngtcp2_conn_writev_datagram` if it has data to send in + * unreliable datagram) to pack them into the same packet. + * Application has to specify the same |conn|, |path|, |pi|, |dest|, + * |destlen|, and |ts| parameters, otherwise the behaviour is + * undefined. The application can change |flags|. + * + * - The function returns one of the following negative error codes: + * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED`, + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`, + * :macro:`NGTCP2_ERR_STREAM_SHUT_WR`. In this case, |*pdatalen| == + * -1 is asserted. Application can still write the stream data of + * the other streams by calling this function (or + * `ngtcp2_conn_writev_datagram` if it has data to send in + * unreliable datagram) to pack them into the same packet. + * Application has to specify the same |conn|, |path|, |pi|, |dest|, + * |destlen|, and |ts| parameters, otherwise the behaviour is + * undefined. The application can change |flags|. + * + * - The other negative error codes might be returned just like + * without :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE`. These errors + * should be treated as a connection error. + * + * When application uses :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` at + * least once, it must not call other ngtcp2 API functions + * (application can still call `ngtcp2_conn_write_connection_close` to + * handle error from this function), just keep calling this function + * (or `ngtcp2_conn_write_pkt`, or `ngtcp2_conn_writev_datagram`) + * until it returns 0, a positive number (which indicates a complete + * packet is ready), or the error codes other than + * :macro:`NGTCP2_ERR_WRITE_MORE`, + * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED`, + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND`, and + * :macro:`NGTCP2_ERR_STREAM_SHUT_WR`. If there is no stream data to + * include, call this function with |stream_id| as -1 to stop + * coalescing and write a packet. + * + * This function returns 0 if it cannot write any frame because buffer + * is too small, or packet is congestion limited. Application should + * keep reading and wait for congestion window to grow. + * + * This function must not be called from inside the callback + * functions. + * + * `ngtcp2_conn_update_pkt_tx_time` must be called after this + * function. Application may call this function multiple times before + * calling `ngtcp2_conn_update_pkt_tx_time`. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` + * Stream does not exist + * :macro:`NGTCP2_ERR_STREAM_SHUT_WR` + * Stream is half closed (local); or stream is being reset. + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * The total length of stream data is too large. + * :macro:`NGTCP2_ERR_STREAM_DATA_BLOCKED` + * Stream is blocked because of flow control. + * :macro:`NGTCP2_ERR_WRITE_MORE` + * (Only when :macro:`NGTCP2_WRITE_STREAM_FLAG_MORE` is specified) + * Application can call this function to pack more stream data + * into the same packet. See above to know how it works. + * + * In general, if the error code which satisfies + * `ngtcp2_err_is_fatal(err) <ngtcp2_err_is_fatal>` != 0 is returned, + * the application should just close the connection by calling + * `ngtcp2_conn_write_connection_close` or just delete the QUIC + * connection using `ngtcp2_conn_del`. It is undefined to call the + * other library functions. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_writev_stream_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen, + uint32_t flags, int64_t stream_id, const ngtcp2_vec *datav, size_t datavcnt, + ngtcp2_tstamp ts); + +/** + * @macrosection + * + * Write datagram flags + */ + +/** + * @macro + * + * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_NONE` indicates no flag set. + */ +#define NGTCP2_WRITE_DATAGRAM_FLAG_NONE 0x00u + +/** + * @macro + * + * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE` indicates that more data + * may come and should be coalesced into the same packet if possible. + */ +#define NGTCP2_WRITE_DATAGRAM_FLAG_MORE 0x01u + +/** + * @function + * + * `ngtcp2_conn_writev_datagram` writes a packet containing unreliable + * data in DATAGRAM frame. The buffer of the packet is pointed by + * |dest| of length |destlen|. This function performs QUIC handshake + * as well. + * + * |destlen| should be at least + * :member:`ngtcp2_settings.max_tx_udp_payload_size`. + * + * For |path| and |pi| parameters, refer to + * `ngtcp2_conn_writev_stream`. + * + * If the given data is written to the buffer, nonzero value is + * assigned to |*paccepted| if it is not NULL. The data in DATAGRAM + * frame cannot be fragmented; writing partial data is not possible. + * + * |dgram_id| is an opaque identifier which should uniquely identify + * the given DATAGRAM. It is passed to :type:`ngtcp2_ack_datagram` + * callback when a packet that contains DATAGRAM frame is + * acknowledged. It is passed to :type:`ngtcp2_lost_datagram` + * callback when a packet that contains DATAGRAM frame is declared + * lost. If an application uses neither of those callbacks, it can + * sets 0 to this parameter. + * + * This function might write other frames other than DATAGRAM, just + * like `ngtcp2_conn_writev_stream`. + * + * If the function returns 0, it means that no more data cannot be + * sent because of congestion control limit; or, data does not fit + * into the provided buffer; or, a local endpoint, as a server, is + * unable to send data because of its amplification limit. In this + * case, |*paccepted| is assigned zero if it is not NULL. + * + * If :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE` is set in |flags|, + * there are 3 outcomes: + * + * - The function returns the written length of packet just like + * without :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE`. This is + * because packet is nearly full and the library decided to make a + * complete packet. |*paccepted| might be zero or nonzero. + * + * - The function returns :macro:`NGTCP2_ERR_WRITE_MORE`. In this + * case, |*paccepted| != 0 is asserted. This indicates that + * application can call this function with another unreliable data + * (or `ngtcp2_conn_writev_stream` if it has stream data to send) to + * pack them into the same packet. Application has to specify the + * same |conn|, |path|, |pi|, |dest|, |destlen|, and |ts| + * parameters, otherwise the behaviour is undefined. The + * application can change |flags|. + * + * - The other error might be returned just like without + * :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE`. + * + * When application sees :macro:`NGTCP2_ERR_WRITE_MORE`, it must not + * call other ngtcp2 API functions (application can still call + * `ngtcp2_conn_write_connection_close` to handle error from this + * function). Just keep calling `ngtcp2_conn_writev_datagram`, + * `ngtcp2_conn_writev_stream` or `ngtcp2_conn_write_pkt` until it + * returns a positive number (which indicates a complete packet is + * ready). + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + * :macro:`NGTCP2_ERR_WRITE_MORE` + * (Only when :macro:`NGTCP2_WRITE_DATAGRAM_FLAG_MORE` is + * specified) Application can call this function to pack more data + * into the same packet. See above to know how it works. + * :macro:`NGTCP2_ERR_INVALID_STATE` + * A remote endpoint did not express the DATAGRAM frame support. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * The provisional DATAGRAM frame size exceeds the maximum + * DATAGRAM frame size that a remote endpoint can receive. + * + * In general, if the error code which satisfies + * `ngtcp2_err_is_fatal(err) <ngtcp2_err_is_fatal>` != 0 is returned, + * the application should just close the connection by calling + * `ngtcp2_conn_write_connection_close` or just delete the QUIC + * connection using `ngtcp2_conn_del`. It is undefined to call the + * other library functions. + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_writev_datagram_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted, + uint32_t flags, uint64_t dgram_id, const ngtcp2_vec *datav, size_t datavcnt, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_is_in_closing_period` returns nonzero if |conn| is in + * the closing period. + */ +NGTCP2_EXTERN int ngtcp2_conn_is_in_closing_period(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_is_in_draining_period` returns nonzero if |conn| is in + * the draining period. + */ +NGTCP2_EXTERN int ngtcp2_conn_is_in_draining_period(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_extend_max_stream_offset` extends stream's max stream + * data value by |datalen|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_extend_max_stream_offset(ngtcp2_conn *conn, + int64_t stream_id, + uint64_t datalen); + +/** + * @function + * + * `ngtcp2_conn_extend_max_offset` extends max data offset by + * |datalen|. + */ +NGTCP2_EXTERN void ngtcp2_conn_extend_max_offset(ngtcp2_conn *conn, + uint64_t datalen); + +/** + * @function + * + * `ngtcp2_conn_extend_max_streams_bidi` extends the number of maximum + * local bidirectional streams that a remote endpoint can open by |n|. + * + * The library does not increase maximum stream limit automatically. + * The exception is when a stream is closed without + * :type:`ngtcp2_stream_open` callback being called. In this case, + * stream limit is increased automatically. + */ +NGTCP2_EXTERN void ngtcp2_conn_extend_max_streams_bidi(ngtcp2_conn *conn, + size_t n); + +/** + * @function + * + * `ngtcp2_conn_extend_max_streams_uni` extends the number of maximum + * local unidirectional streams that a remote endpoint can open by + * |n|. + * + * The library does not increase maximum stream limit automatically. + * The exception is when a stream is closed without + * :type:`ngtcp2_stream_open` callback being called. In this case, + * stream limit is increased automatically. + */ +NGTCP2_EXTERN void ngtcp2_conn_extend_max_streams_uni(ngtcp2_conn *conn, + size_t n); + +/** + * @function + * + * `ngtcp2_conn_get_dcid` returns the non-NULL pointer to destination + * connection ID. If no destination connection ID is present, the + * return value is not ``NULL``, and its datalen field is 0. + */ +NGTCP2_EXTERN const ngtcp2_cid *ngtcp2_conn_get_dcid(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_client_initial_dcid` returns the non-NULL pointer + * to the Destination Connection ID that client sent in its Initial + * packet. + */ +NGTCP2_EXTERN const ngtcp2_cid * +ngtcp2_conn_get_client_initial_dcid(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_num_scid` returns the number of source connection + * IDs which the local endpoint has provided to the peer and have not + * retired. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_num_scid(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_scid` writes the all source connection IDs which + * the local endpoint has provided to the peer and have not retired in + * |dest|. The buffer pointed by |dest| must have + * ``sizeof(ngtcp2_cid) * n`` bytes available, where n is the return + * value of `ngtcp2_conn_get_num_scid()`. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_scid(ngtcp2_conn *conn, ngtcp2_cid *dest); + +/** + * @function + * + * `ngtcp2_conn_get_num_active_dcid` returns the number of the active + * destination connection ID. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_num_active_dcid(ngtcp2_conn *conn); + +/** + * @struct + * + * :type:`ngtcp2_cid_token` is the convenient struct to store + * Connection ID, its associated path, and stateless reset token. + */ +typedef struct ngtcp2_cid_token { + /** + * :member:`seq` is the sequence number of this Connection ID. + */ + uint64_t seq; + /** + * :member:`cid` is Connection ID. + */ + ngtcp2_cid cid; + /** + * :member:`ps` is the path which is associated to this Connection + * ID. + */ + ngtcp2_path_storage ps; + /** + * :member:`token` is the stateless reset token for this Connection + * ID. + */ + uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN]; + /** + * :member:`token_present` is nonzero if token contains stateless + * reset token. + */ + uint8_t token_present; +} ngtcp2_cid_token; + +/** + * @function + * + * `ngtcp2_conn_get_active_dcid` writes the all active destination + * connection IDs and tokens to |dest|. The buffer pointed by |dest| + * must have ``sizeof(ngtcp2_cid_token) * n`` bytes available, where n + * is the return value of `ngtcp2_conn_get_num_active_dcid()`. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, + ngtcp2_cid_token *dest); + +/** + * @function + * + * `ngtcp2_conn_get_client_chosen_version` returns the client chosen + * version. + */ +NGTCP2_EXTERN uint32_t ngtcp2_conn_get_client_chosen_version(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_negotiated_version` returns the negotiated version. + * + * Until the version is negotiated, this function returns 0. + */ +NGTCP2_EXTERN uint32_t ngtcp2_conn_get_negotiated_version(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_early_data_rejected` tells |conn| that early data was + * rejected by a server, or client decided not to attempt early data + * for some reason. |conn| discards the following connection states: + * + * - Any opended streams. + * - Stream identifier allocations. + * - Max data extended by `ngtcp2_conn_extend_max_offset`. + * - Max bidi streams extended by `ngtcp2_conn_extend_max_streams_bidi`. + * - Max uni streams extended by `ngtcp2_conn_extend_max_streams_uni`. + * + * Application which wishes to retransmit early data, it has to open + * streams and send stream data again. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +NGTCP2_EXTERN int ngtcp2_conn_early_data_rejected(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_early_data_rejected` returns nonzero if + * `ngtcp2_conn_early_data_rejected` has been called. + */ +NGTCP2_EXTERN int ngtcp2_conn_get_early_data_rejected(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_conn_stat` assigns connection statistics data to + * |*cstat|. + */ +NGTCP2_EXTERN void ngtcp2_conn_get_conn_stat_versioned(ngtcp2_conn *conn, + int conn_stat_version, + ngtcp2_conn_stat *cstat); + +/** + * @function + * + * `ngtcp2_conn_submit_crypto_data` submits crypto stream data |data| + * of length |datalen| to the library for transmission. The + * encryption level is given in |crypto_level|. + * + * The library makes a copy of the buffer pointed by |data| of length + * |datalen|. Application can discard |data|. + */ +NGTCP2_EXTERN int +ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + const uint8_t *data, const size_t datalen); + +/** + * @function + * + * `ngtcp2_conn_submit_new_token` submits address validation token. + * It is sent in NEW_TOKEN frame. Only server can call this function. + * |tokenlen| must not be 0. + * + * This function makes a copy of the buffer pointed by |token| of + * length |tokenlen|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn, + const uint8_t *token, + size_t tokenlen); + +/** + * @function + * + * `ngtcp2_conn_set_local_addr` sets local endpoint address |addr| to + * the current path of |conn|. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_local_addr(ngtcp2_conn *conn, + const ngtcp2_addr *addr); + +/** + * @function + * + * `ngtcp2_conn_set_path_user_data` sets the |path_user_data| to the + * current path (see :member:`ngtcp2_path.user_data`). + */ +NGTCP2_EXTERN void ngtcp2_conn_set_path_user_data(ngtcp2_conn *conn, + void *path_user_data); + +/** + * @function + * + * `ngtcp2_conn_get_path` returns the current path. + */ +NGTCP2_EXTERN const ngtcp2_path *ngtcp2_conn_get_path(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_max_tx_udp_payload_size` returns the maximum UDP + * payload size that this local endpoint would send. This is the + * value of :member:`ngtcp2_settings.max_tx_udp_payload_size` that is + * passed to `ngtcp2_conn_client_new` or `ngtcp2_conn_server_new`. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_max_tx_udp_payload_size(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_path_max_tx_udp_payload_size` returns the maximum + * UDP payload size for the current path. If + * :member:`ngtcp2_settings.no_tx_udp_payload_size_shaping` is set to + * nonzero, this function is equivalent to + * `ngtcp2_conn_get_max_tx_udp_payload_size`. Otherwise, it returns + * the maximum UDP payload size that is probed for the current path. + */ +NGTCP2_EXTERN size_t +ngtcp2_conn_get_path_max_tx_udp_payload_size(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_initiate_immediate_migration` starts connection + * migration to the given |path|. Only client can initiate migration. + * This function does immediate migration; while the path validation + * is nonetheless performed, this function does not wait for it to + * succeed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_INVALID_STATE` + * Migration is disabled; or handshake is not yet confirmed; or + * client is migrating to server's preferred address. + * :macro:`NGTCP2_ERR_CONN_ID_BLOCKED` + * No unused connection ID is available. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |local_addr| equals the current local address. + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + */ +NGTCP2_EXTERN int ngtcp2_conn_initiate_immediate_migration( + ngtcp2_conn *conn, const ngtcp2_path *path, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_initiate_migration` starts connection migration to the + * given |path|. Only client can initiate migration. Unlike + * `ngtcp2_conn_initiate_immediate_migration`, this function starts a + * path validation with a new path and migrate to the new path after + * successful path validation. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_INVALID_STATE` + * Migration is disabled; or handshake is not yet confirmed; or + * client is migrating to server's preferred address. + * :macro:`NGTCP2_ERR_CONN_ID_BLOCKED` + * No unused connection ID is available. + * :macro:`NGTCP2_ERR_INVALID_ARGUMENT` + * |local_addr| equals the current local address. + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + */ +NGTCP2_EXTERN int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, + const ngtcp2_path *path, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_get_max_local_streams_uni` returns the cumulative + * number of streams which local endpoint can open. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_max_local_streams_uni(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_max_data_left` returns the number of bytes that + * this local endpoint can send in this connection. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_max_data_left(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_max_stream_data_left` returns the number of bytes + * that this local endpoint can send to a stream identified by + * |stream_id|. If no such stream is found, this function returns 0. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_max_stream_data_left(ngtcp2_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `ngtcp2_conn_get_streams_bidi_left` returns the number of + * bidirectional streams which the local endpoint can open without + * violating stream concurrency limit. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_streams_bidi_left(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_streams_uni_left` returns the number of + * unidirectional streams which the local endpoint can open without + * violating stream concurrency limit. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_streams_uni_left(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_cwnd_left` returns the cwnd minus the number of + * bytes in flight on the current path. If the former is smaller than + * the latter, this function returns 0. + */ +NGTCP2_EXTERN uint64_t ngtcp2_conn_get_cwnd_left(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_initial_crypto_ctx` sets |ctx| for Initial packet + * encryption. The passed data will be passed to + * :type:`ngtcp2_encrypt`, :type:`ngtcp2_decrypt` and + * :type:`ngtcp2_hp_mask` callbacks. + */ +NGTCP2_EXTERN void +ngtcp2_conn_set_initial_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx); + +/** + * @function + * + * `ngtcp2_conn_get_initial_crypto_ctx` returns + * :type:`ngtcp2_crypto_ctx` object for Initial packet encryption. + */ +NGTCP2_EXTERN const ngtcp2_crypto_ctx * +ngtcp2_conn_get_initial_crypto_ctx(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_crypto_ctx` sets |ctx| for Handshake/1RTT packet + * encryption. The passed data will be passed to + * :type:`ngtcp2_encrypt`, :type:`ngtcp2_decrypt` and + * :type:`ngtcp2_hp_mask` callbacks. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx); + +/** + * @function + * + * `ngtcp2_conn_get_tls_native_handle` returns TLS native handle set by + * `ngtcp2_conn_set_tls_native_handle()`. + */ +NGTCP2_EXTERN void *ngtcp2_conn_get_tls_native_handle(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_tls_native_handle` sets TLS native handle + * |tls_native_handle| to |conn|. Internally, it is used as an opaque + * pointer. + */ +NGTCP2_EXTERN void ngtcp2_conn_set_tls_native_handle(ngtcp2_conn *conn, + void *tls_native_handle); + +/** + * @function + * + * `ngtcp2_conn_set_retry_aead` sets |aead| and |aead_ctx| for Retry + * integrity tag verification. |aead| must be AEAD_AES_128_GCM. + * |aead_ctx| must be initialized with :macro:`NGTCP2_RETRY_KEY` as + * encryption key. This function must be called if |conn| is + * initialized as client. Server does not verify the tag and has no + * need to call this function. + * + * If this function succeeds, |conn| takes ownership of |aead_ctx|. + * :type:`ngtcp2_delete_crypto_aead_ctx` will be called to delete this + * object when it is no longer used. If this function fails, the + * caller is responsible to delete it. + */ +NGTCP2_EXTERN void +ngtcp2_conn_set_retry_aead(ngtcp2_conn *conn, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx); + +/** + * @function + * + * `ngtcp2_conn_get_crypto_ctx` returns :type:`ngtcp2_crypto_ctx` + * object for Handshake/1RTT packet encryption. + */ +NGTCP2_EXTERN const ngtcp2_crypto_ctx * +ngtcp2_conn_get_crypto_ctx(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_early_crypto_ctx` sets |ctx| for 0RTT packet + * encryption. The passed data will be passed to + * :type:`ngtcp2_encrypt`, :type:`ngtcp2_decrypt` and + * :type:`ngtcp2_hp_mask` callbacks. + */ +NGTCP2_EXTERN void +ngtcp2_conn_set_early_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx); + +/** + * @function + * + * `ngtcp2_conn_get_early_crypto_ctx` returns + * :type:`ngtcp2_crypto_ctx` object for 0RTT packet encryption. + */ +NGTCP2_EXTERN const ngtcp2_crypto_ctx * +ngtcp2_conn_get_early_crypto_ctx(ngtcp2_conn *conn); + +/** + * @enum + * + * :type:`ngtcp2_connection_close_error_code_type` defines connection + * error code type. + */ +typedef enum ngtcp2_connection_close_error_code_type { + /** + * :enum:`NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT` + * indicates the error code is QUIC transport error code. + */ + NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT, + /** + * :enum:`NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION` + * indicates the error code is application error code. + */ + NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION, + /** + * :enum:`NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_VERSION_NEGOTIATION` + * is a special case of QUIC transport error, and it indicates that + * client receives Version Negotiation packet. + */ + NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_VERSION_NEGOTIATION, + /** + * :enum:`NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE` + * is a special case of QUIC transport error, and it indicates that + * connection is closed because of idle timeout. + */ + NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE +} ngtcp2_connection_close_error_code_type; + +/** + * @struct + * + * :type:`ngtcp2_connection_close_error` contains connection + * error code, its type, and the optional reason phrase. + */ +typedef struct ngtcp2_connection_close_error { + /** + * :member:`type` is the type of :member:`error_code`. + */ + ngtcp2_connection_close_error_code_type type; + /** + * :member:`error_code` is the error code for connection closure. + */ + uint64_t error_code; + /** + * :member:`frame_type` is the type of QUIC frame which triggers + * this connection error. This field is set to 0 if the frame type + * is unknown. + */ + uint64_t frame_type; + /** + * :member:`reason` points to the buffer which contains a reason + * phrase. It may be NULL if there is no reason phrase. If it is + * received from a remote endpoint, it is truncated to at most 1024 + * bytes. + */ + uint8_t *reason; + /** + * :member:`reasonlen` is the length of data pointed by + * :member:`reason`. + */ + size_t reasonlen; +} ngtcp2_connection_close_error; + +/** + * @function + * + * `ngtcp2_connection_close_error_default` initializes |ccerr| with + * the default values. It sets the following fields: + * + * - :member:`type <ngtcp2_connection_close_error.type>` = + * :enum:`ngtcp2_connection_close_error_code_type.NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT` + * - :member:`error_code <ngtcp2_connection_close_error.error_code>` = + * :macro:`NGTCP2_NO_ERROR`. + * - :member:`frame_type <ngtcp2_connection_close_error.frame_type>` = + * 0 + * - :member:`reason <ngtcp2_connection_close_error.reason>` = NULL + * - :member:`reasonlen <ngtcp2_connection_close_error.reasonlen>` = 0 + */ +NGTCP2_EXTERN void +ngtcp2_connection_close_error_default(ngtcp2_connection_close_error *ccerr); + +/** + * @function + * + * `ngtcp2_connection_close_error_set_transport_error` sets + * :member:`ccerr->type <ngtcp2_connection_close_error.type>` to + * :enum:`ngtcp2_connection_close_error_code_type.NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT`, + * and :member:`ccerr->error_code + * <ngtcp2_connection_close_error.error_code>` to |error_code|. + * |reason| is the reason phrase of length |reasonlen|. This function + * does not make a copy of the reason phrase. + */ +NGTCP2_EXTERN void ngtcp2_connection_close_error_set_transport_error( + ngtcp2_connection_close_error *ccerr, uint64_t error_code, + const uint8_t *reason, size_t reasonlen); + +/** + * @function + * + * `ngtcp2_connection_close_error_set_transport_error_liberr` sets + * type and error_code based on |liberr|. + * + * If |liberr| is :macro:`NGTCP2_ERR_RECV_VERSION_NEGOTIATION`, + * :member:`ccerr->type <ngtcp2_connection_close_error.type>` is set + * to + * :enum:`ngtcp2_connection_close_error_code_type.NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_VERSION_NEGOTIATION`, + * and :member:`ccerr->error_code + * <ngtcp2_connection_close_error.error_code>` to + * :macro:`NGTCP2_NO_ERROR`. If |liberr| is + * :macro:`NGTCP2_ERR_IDLE_CLOSE`, :member:`ccerr->type + * <ngtcp2_connection_close_error.type>` is set to + * :enum:`ngtcp2_connection_close_error_code_type.NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE`, + * and :member:`ccerr->error_code + * <ngtcp2_connection_close_error.error_code>` to + * :macro:`NGTCP2_NO_ERROR`. Otherwise, :member:`ccerr->type + * <ngtcp2_connection_close_error.type>` is set to + * :enum:`ngtcp2_connection_close_error_code_type.NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT`, + * and :member:`ccerr->error_code + * <ngtcp2_connection_close_error.error_code>` is set to an error code + * inferred by |liberr| (see + * `ngtcp2_err_infer_quic_transport_error_code`). |reason| is the + * reason phrase of length |reasonlen|. This function does not make a + * copy of the reason phrase. + */ +NGTCP2_EXTERN void ngtcp2_connection_close_error_set_transport_error_liberr( + ngtcp2_connection_close_error *ccerr, int liberr, const uint8_t *reason, + size_t reasonlen); + +/** + * @function + * + * `ngtcp2_connection_close_error_set_transport_error_tls_alert` sets + * :member:`ccerr->type <ngtcp2_connection_close_error.type>` to + * :enum:`ngtcp2_connection_close_error_code_type.NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT`, + * and :member:`ccerr->error_code + * <ngtcp2_connection_close_error.error_code>` to bitwise-OR of + * :macro:`NGTCP2_CRYPTO_ERROR` and |tls_alert|. |reason| is the + * reason phrase of length |reasonlen|. This function does not make a + * copy of the reason phrase. + */ +NGTCP2_EXTERN void ngtcp2_connection_close_error_set_transport_error_tls_alert( + ngtcp2_connection_close_error *ccerr, uint8_t tls_alert, + const uint8_t *reason, size_t reasonlen); + +/** + * @function + * + * `ngtcp2_connection_close_error_set_application_error` sets + * :member:`ccerr->type <ngtcp2_connection_close_error.type>` to + * :enum:`ngtcp2_connection_close_error_code_type.NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION`, + * and :member:`ccerr->error_code + * <ngtcp2_connection_close_error.error_code>` to |error_code|. + * |reason| is the reason phrase of length |reasonlen|. This function + * does not make a copy of the reason phrase. + */ +NGTCP2_EXTERN void ngtcp2_connection_close_error_set_application_error( + ngtcp2_connection_close_error *ccerr, uint64_t error_code, + const uint8_t *reason, size_t reasonlen); + +/** + * @function + * + * `ngtcp2_conn_write_connection_close` writes a packet which contains + * CONNECTION_CLOSE frame(s) (type 0x1c or 0x1d) in the buffer pointed + * by |dest| whose capacity is |destlen|. + * + * For client, |destlen| should be at least + * :macro:`NGTCP2_MAX_UDP_PAYLOAD_SIZE`. + * + * If |path| is not ``NULL``, this function stores the network path + * with which the packet should be sent. Each addr field must point + * to the buffer which should be at least ``sizeof(struct + * sockaddr_storage)`` bytes long. The assignment might not be done + * if nothing is written to |dest|. + * + * If |pi| is not ``NULL``, this function stores packet metadata in it + * if it succeeds. The metadata includes ECN markings. + * + * If :member:`ccerr->type <ngtcp2_connection_close_error.type>` == + * :enum:`ngtcp2_connection_close_error_code_type.NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT`, + * this function sends CONNECTION_CLOSE (type 0x1c) frame. If + * :member:`ccerr->type <ngtcp2_connection_close_error.type>` == + * :enum:`ngtcp2_connection_close_error_code_type.NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION`, + * it sends CONNECTION_CLOSE (type 0x1d) frame. Otherwise, it does + * not produce any data, and returns 0. + * + * This function must not be called from inside the callback + * functions. + * + * At the moment, successful call to this function makes connection + * close. We may change this behaviour in the future to allow + * graceful shutdown. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small + * :macro:`NGTCP2_ERR_INVALID_STATE` + * The current state does not allow sending CONNECTION_CLOSE. + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +NGTCP2_EXTERN ngtcp2_ssize ngtcp2_conn_write_connection_close_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, + const ngtcp2_connection_close_error *ccerr, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_get_connection_close_error` stores the received + * connection close error in |ccerr|. + */ +NGTCP2_EXTERN void +ngtcp2_conn_get_connection_close_error(ngtcp2_conn *conn, + ngtcp2_connection_close_error *ccerr); + +/** + * @function + * + * `ngtcp2_conn_is_local_stream` returns nonzero if |stream_id| denotes the + * stream which a local endpoint issues. + */ +NGTCP2_EXTERN int ngtcp2_conn_is_local_stream(ngtcp2_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `ngtcp2_conn_is_server` returns nonzero if |conn| is initialized as + * server. + */ +NGTCP2_EXTERN int ngtcp2_conn_is_server(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_after_retry` returns nonzero if |conn| as a client has + * received Retry packet from server and successfully validated it. + */ +NGTCP2_EXTERN int ngtcp2_conn_after_retry(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_stream_user_data` sets |stream_user_data| to the + * stream identified by |stream_id|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_STREAM_NOT_FOUND` + * Stream does not exist + */ +NGTCP2_EXTERN int ngtcp2_conn_set_stream_user_data(ngtcp2_conn *conn, + int64_t stream_id, + void *stream_user_data); + +/** + * @function + * + * `ngtcp2_conn_update_pkt_tx_time` sets the time instant of the next + * packet transmission. This function must be called after (multiple + * invocation of) `ngtcp2_conn_writev_stream`. If packet aggregation + * (e.g., packet batching, GSO) is used, call this function after all + * aggregated datagrams are sent, which indicates multiple invocation + * of `ngtcp2_conn_writev_stream`. + */ +NGTCP2_EXTERN void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_get_send_quantum` returns the maximum number of bytes + * that can be sent in one go without packet spacing. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_stream_loss_count` returns the number of packets + * that contain STREAM frame for a stream identified by |stream_id| + * and are declared to be lost. The number may include the spurious + * losses. If no stream identified by |stream_id| is found, this + * function returns 0. + */ +NGTCP2_EXTERN size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn, + int64_t stream_id); + +/** + * @function + * + * `ngtcp2_strerror` returns the text representation of |liberr|. + * |liberr| must be one of ngtcp2 library error codes (which is + * defined as NGTCP2_ERR_* macro, such as + * :macro:`NGTCP2_ERR_DECRYPT`). + */ +NGTCP2_EXTERN const char *ngtcp2_strerror(int liberr); + +/** + * @function + * + * `ngtcp2_err_is_fatal` returns nonzero if |liberr| is a fatal error. + * |liberr| must be one of ngtcp2 library error codes (which is + * defined as NGTCP2_ERR_* macro, such as + * :macro:`NGTCP2_ERR_DECRYPT`). + */ +NGTCP2_EXTERN int ngtcp2_err_is_fatal(int liberr); + +/** + * @function + * + * `ngtcp2_err_infer_quic_transport_error_code` returns a QUIC + * transport error code which corresponds to |liberr|. |liberr| must + * be one of ngtcp2 library error codes (which is defined as + * NGTCP2_ERR_* macro, such as :macro:`NGTCP2_ERR_DECRYPT`). + */ +NGTCP2_EXTERN uint64_t ngtcp2_err_infer_quic_transport_error_code(int liberr); + +/** + * @function + * + * `ngtcp2_addr_init` initializes |dest| with the given arguments and + * returns |dest|. + */ +NGTCP2_EXTERN ngtcp2_addr *ngtcp2_addr_init(ngtcp2_addr *dest, + const ngtcp2_sockaddr *addr, + ngtcp2_socklen addrlen); + +/** + * @function + * + * `ngtcp2_addr_copy_byte` copies |addr| of length |addrlen| into the + * buffer pointed by :member:`dest->addr <ngtcp2_addr.addr>`. + * :member:`dest->addrlen <ngtcp2_addr.addrlen>` is updated to have + * |addrlen|. This function assumes that :member:`dest->addr + * <ngtcp2_addr.addr>` points to a buffer which has a sufficient + * capacity to store the copy. + */ +NGTCP2_EXTERN void ngtcp2_addr_copy_byte(ngtcp2_addr *dest, + const ngtcp2_sockaddr *addr, + ngtcp2_socklen addrlen); + +/** + * @function + * + * `ngtcp2_path_storage_init` initializes |ps| with the given + * arguments. This function copies |local_addr| and |remote_addr|. + */ +NGTCP2_EXTERN void ngtcp2_path_storage_init(ngtcp2_path_storage *ps, + const ngtcp2_sockaddr *local_addr, + ngtcp2_socklen local_addrlen, + const ngtcp2_sockaddr *remote_addr, + ngtcp2_socklen remote_addrlen, + void *user_data); + +/** + * @function + * + * `ngtcp2_path_storage_zero` initializes |ps| with the zero length + * addresses. + */ +NGTCP2_EXTERN void ngtcp2_path_storage_zero(ngtcp2_path_storage *ps); + +/** + * @function + * + * `ngtcp2_settings_default` initializes |settings| with the default + * values. First this function fills |settings| with 0 and set the + * default value to the following fields: + * + * * :type:`cc_algo <ngtcp2_settings.cc_algo>` = + * :enum:`ngtcp2_cc_algo.NGTCP2_CC_ALGO_CUBIC` + * * :type:`initial_rtt <ngtcp2_settings.initial_rtt>` = + * :macro:`NGTCP2_DEFAULT_INITIAL_RTT` + * * :type:`ack_thresh <ngtcp2_settings.ack_thresh>` = 2 + * * :type:`max_tx_udp_payload_size + * <ngtcp2_settings.max_tx_udp_payload_size>` = 1452 + * * :type:`handshake_timeout <ngtcp2_settings.handshake_timeout>` = + * :macro:`NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT`. + */ +NGTCP2_EXTERN void ngtcp2_settings_default_versioned(int settings_version, + ngtcp2_settings *settings); + +/** + * @function + * + * `ngtcp2_transport_params_default` initializes |params| with the + * default values. First this function fills |params| with 0 and set + * the default value to the following fields: + * + * * :type:`max_udp_payload_size + * <ngtcp2_transport_params.max_udp_payload_size>` = + * :macro:`NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE` + * * :type:`ack_delay_exponent + * <ngtcp2_transport_params.ack_delay_exponent>` = + * :macro:`NGTCP2_DEFAULT_ACK_DELAY_EXPONENT` + * * :type:`max_ack_delay <ngtcp2_transport_params.max_ack_delay>` = + * :macro:`NGTCP2_DEFAULT_MAX_ACK_DELAY` + * * :type:`active_connection_id_limit + * <ngtcp2_transport_params.active_connection_id_limit>` = + * :macro:`NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT` + */ +NGTCP2_EXTERN void +ngtcp2_transport_params_default_versioned(int transport_params_version, + ngtcp2_transport_params *params); + +/** + * @function + * + * `ngtcp2_mem_default` returns the default, system standard memory + * allocator. + */ +NGTCP2_EXTERN const ngtcp2_mem *ngtcp2_mem_default(void); + +/** + * @macrosection + * + * ngtcp2_info macros + */ + +/** + * @macro + * + * :macro:`NGTCP2_VERSION_AGE` is the age of :type:`ngtcp2_info` + */ +#define NGTCP2_VERSION_AGE 1 + +/** + * @struct + * + * :type:`ngtcp2_info` is what `ngtcp2_version()` returns. It holds + * information about the particular ngtcp2 version. + */ +typedef struct ngtcp2_info { + /** + * :member:`age` is the age of this struct. This instance of ngtcp2 + * sets it to :macro:`NGTCP2_VERSION_AGE` but a future version may + * bump it and add more struct fields at the bottom + */ + int age; + /** + * :member:`version_num` is the :macro:`NGTCP2_VERSION_NUM` number + * (since age ==1) + */ + int version_num; + /** + * :member:`version_str` points to the :macro:`NGTCP2_VERSION` + * string (since age ==1) + */ + const char *version_str; + /* -------- the above fields all exist when age == 1 */ +} ngtcp2_info; + +/** + * @function + * + * `ngtcp2_version` returns a pointer to a ngtcp2_info struct with + * version information about the run-time library in use. The + * |least_version| argument can be set to a 24 bit numerical value for + * the least accepted version number and if the condition is not met, + * this function will return a ``NULL``. Pass in 0 to skip the + * version checking. + */ +NGTCP2_EXTERN const ngtcp2_info *ngtcp2_version(int least_version); + +/** + * @function + * + * `ngtcp2_is_bidi_stream` returns nonzero if |stream_id| denotes + * bidirectional stream. + */ +NGTCP2_EXTERN int ngtcp2_is_bidi_stream(int64_t stream_id); + +/** + * @function + * + * `ngtcp2_path_copy` copies |src| into |dest|. This function assumes + * that |dest| has enough buffer to store the deep copy of + * :member:`src->local <ngtcp2_path.local>` and :member:`src->remote + * <ngtcp2_path.remote>`. + */ +NGTCP2_EXTERN void ngtcp2_path_copy(ngtcp2_path *dest, const ngtcp2_path *src); + +/** + * @function + * + * `ngtcp2_path_eq` returns nonzero if |a| and |b| shares the same + * local and remote addresses. + */ +NGTCP2_EXTERN int ngtcp2_path_eq(const ngtcp2_path *a, const ngtcp2_path *b); + +/** + * @function + * + * `ngtcp2_is_supported_version` returns nonzero if the library supports + * QUIC version |version|. + */ +NGTCP2_EXTERN int ngtcp2_is_supported_version(uint32_t version); + +/* + * @function + * + * `ngtcp2_is_reserved_version` returns nonzero if |version| is a + * reserved version. + */ +NGTCP2_EXTERN int ngtcp2_is_reserved_version(uint32_t version); + +/** + * @function + * + * `ngtcp2_select_version` selects and returns a version from the + * version set |offered_versions| of |offered_versionslen| elements. + * |preferred_versions| of |preferred_versionslen| elements specifies + * the preference of versions, which is sorted in the order of + * preference. All versions included in |preferred_versions| must be + * supported by the library, that is, passing a version to + * `ngtcp2_is_supported_version` must return nonzero. This function + * is intended to be used by client when it receives Version + * Negotiation packet. If no version is selected, this function + * returns 0. + */ +NGTCP2_EXTERN uint32_t ngtcp2_select_version(const uint32_t *preferred_versions, + size_t preferred_versionslen, + const uint32_t *offered_versions, + size_t offered_versionslen); + +/* + * Versioned function wrappers + */ + +/* + * `ngtcp2_conn_read_pkt` is a wrapper around + * `ngtcp2_conn_read_pkt_versioned` to set the correct struct version. + */ +#define ngtcp2_conn_read_pkt(CONN, PATH, PI, PKT, PKTLEN, TS) \ + ngtcp2_conn_read_pkt_versioned((CONN), (PATH), NGTCP2_PKT_INFO_VERSION, \ + (PI), (PKT), (PKTLEN), (TS)) + +/* + * `ngtcp2_conn_write_pkt` is a wrapper around + * `ngtcp2_conn_write_pkt_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_write_pkt(CONN, PATH, PI, DEST, DESTLEN, TS) \ + ngtcp2_conn_write_pkt_versioned((CONN), (PATH), NGTCP2_PKT_INFO_VERSION, \ + (PI), (DEST), (DESTLEN), (TS)) + +/* + * `ngtcp2_conn_write_stream` is a wrapper around + * `ngtcp2_conn_write_stream_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_write_stream(CONN, PATH, PI, DEST, DESTLEN, PDATALEN, \ + FLAGS, STREAM_ID, DATA, DATALEN, TS) \ + ngtcp2_conn_write_stream_versioned( \ + (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), \ + (PDATALEN), (FLAGS), (STREAM_ID), (DATA), (DATALEN), (TS)) + +/* + * `ngtcp2_conn_writev_stream` is a wrapper around + * `ngtcp2_conn_writev_stream_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_writev_stream(CONN, PATH, PI, DEST, DESTLEN, PDATALEN, \ + FLAGS, STREAM_ID, DATAV, DATAVCNT, TS) \ + ngtcp2_conn_writev_stream_versioned( \ + (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), \ + (PDATALEN), (FLAGS), (STREAM_ID), (DATAV), (DATAVCNT), (TS)) + +/* + * `ngtcp2_conn_writev_datagram` is a wrapper around + * `ngtcp2_conn_writev_datagram_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_writev_datagram(CONN, PATH, PI, DEST, DESTLEN, PACCEPTED, \ + FLAGS, DGRAM_ID, DATAV, DATAVCNT, TS) \ + ngtcp2_conn_writev_datagram_versioned( \ + (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), \ + (PACCEPTED), (FLAGS), (DGRAM_ID), (DATAV), (DATAVCNT), (TS)) + +/* + * `ngtcp2_conn_write_connection_close` is a wrapper around + * `ngtcp2_conn_write_connection_close_versioned` to set the correct + * struct version. + */ +#define ngtcp2_conn_write_connection_close(CONN, PATH, PI, DEST, DESTLEN, \ + CCERR, TS) \ + ngtcp2_conn_write_connection_close_versioned( \ + (CONN), (PATH), NGTCP2_PKT_INFO_VERSION, (PI), (DEST), (DESTLEN), \ + (CCERR), (TS)) + +/* + * `ngtcp2_encode_transport_params` is a wrapper around + * `ngtcp2_encode_transport_params_versioned` to set the correct + * struct version. + */ +#define ngtcp2_encode_transport_params(DEST, DESTLEN, EXTTYPE, PARAMS) \ + ngtcp2_encode_transport_params_versioned( \ + (DEST), (DESTLEN), (EXTTYPE), NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS)) + +/* + * `ngtcp2_decode_transport_params` is a wrapper around + * `ngtcp2_decode_transport_params_versioned` to set the correct + * struct version. + */ +#define ngtcp2_decode_transport_params(PARAMS, EXTTYPE, DATA, DATALEN) \ + ngtcp2_decode_transport_params_versioned( \ + NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS), (EXTTYPE), (DATA), (DATALEN)) + +/* + * `ngtcp2_conn_client_new` is a wrapper around + * `ngtcp2_conn_client_new_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_client_new(PCONN, DCID, SCID, PATH, VERSION, CALLBACKS, \ + SETTINGS, PARAMS, MEM, USER_DATA) \ + ngtcp2_conn_client_new_versioned( \ + (PCONN), (DCID), (SCID), (PATH), (VERSION), NGTCP2_CALLBACKS_VERSION, \ + (CALLBACKS), NGTCP2_SETTINGS_VERSION, (SETTINGS), \ + NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS), (MEM), (USER_DATA)) + +/* + * `ngtcp2_conn_server_new` is a wrapper around + * `ngtcp2_conn_server_new_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_server_new(PCONN, DCID, SCID, PATH, VERSION, CALLBACKS, \ + SETTINGS, PARAMS, MEM, USER_DATA) \ + ngtcp2_conn_server_new_versioned( \ + (PCONN), (DCID), (SCID), (PATH), (VERSION), NGTCP2_CALLBACKS_VERSION, \ + (CALLBACKS), NGTCP2_SETTINGS_VERSION, (SETTINGS), \ + NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS), (MEM), (USER_DATA)) + +/* + * `ngtcp2_conn_set_early_remote_transport_params` is a wrapper around + * `ngtcp2_conn_set_early_remote_transport_params_versioned` to set + * the correct struct version. + */ +#define ngtcp2_conn_set_early_remote_transport_params(CONN, PARAMS) \ + ngtcp2_conn_set_early_remote_transport_params_versioned( \ + (CONN), NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS)) + +/* + * `ngtcp2_conn_set_local_transport_params` is a wrapper around + * `ngtcp2_conn_set_local_transport_params_versioned` to set the + * correct struct version. + */ +#define ngtcp2_conn_set_local_transport_params(CONN, PARAMS) \ + ngtcp2_conn_set_local_transport_params_versioned( \ + (CONN), NGTCP2_TRANSPORT_PARAMS_VERSION, (PARAMS)) + +/* + * `ngtcp2_transport_params_default` is a wrapper around + * `ngtcp2_transport_params_default_versioned` to set the correct + * struct version. + */ +#define ngtcp2_transport_params_default(PARAMS) \ + ngtcp2_transport_params_default_versioned(NGTCP2_TRANSPORT_PARAMS_VERSION, \ + (PARAMS)) + +/* + * `ngtcp2_conn_get_conn_stat` is a wrapper around + * `ngtcp2_conn_get_conn_stat_versioned` to set the correct struct + * version. + */ +#define ngtcp2_conn_get_conn_stat(CONN, CSTAT) \ + ngtcp2_conn_get_conn_stat_versioned((CONN), NGTCP2_CONN_STAT_VERSION, (CSTAT)) + +/* + * `ngtcp2_settings_default` is a wrapper around + * `ngtcp2_settings_default_versioned` to set the correct struct + * version. + */ +#define ngtcp2_settings_default(SETTINGS) \ + ngtcp2_settings_default_versioned(NGTCP2_SETTINGS_VERSION, (SETTINGS)) + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* NGTCP2_H */ diff --git a/lib/includes/ngtcp2/version.h.in b/lib/includes/ngtcp2/version.h.in new file mode 100644 index 0000000..8afe88f --- /dev/null +++ b/lib/includes/ngtcp2/version.h.in @@ -0,0 +1,51 @@ +/* + * ngtcp2 + * + * Copyright (c) 2016 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef VERSION_H +#define VERSION_H + +/** + * @macrosection + * + * Library version macros + */ + +/** + * @macro + * + * Version number of the ngtcp2 library release. + */ +#define NGTCP2_VERSION "@PACKAGE_VERSION@" + +/** + * @macro + * + * Numerical representation of the version number of the ngtcp2 + * library release. This is a 24 bit number with 8 bits for major + * number, 8 bits for minor and 8 bits for patch. Version 1.2.3 + * becomes 0x010203. + */ +#define NGTCP2_VERSION_NUM @PACKAGE_VERSION_NUM@ + +#endif /* VERSION_H */ diff --git a/lib/libngtcp2.pc.in b/lib/libngtcp2.pc.in new file mode 100644 index 0000000..6737391 --- /dev/null +++ b/lib/libngtcp2.pc.in @@ -0,0 +1,33 @@ +# ngtcp2 + +# Copyright (c) 2016 ngtcp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libngtcp2 +Description: ngtcp2 library +URL: https://github.com/ngtcp2/ngtcp2 +Version: @VERSION@ +Libs: -L${libdir} -lngtcp2 +Cflags: -I${includedir} diff --git a/lib/ngtcp2_acktr.c b/lib/ngtcp2_acktr.c new file mode 100644 index 0000000..3f1f9b3 --- /dev/null +++ b/lib/ngtcp2_acktr.c @@ -0,0 +1,335 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_acktr.h" + +#include <assert.h> + +#include "ngtcp2_macro.h" + +static void acktr_entry_init(ngtcp2_acktr_entry *ent, int64_t pkt_num, + ngtcp2_tstamp tstamp) { + ent->pkt_num = pkt_num; + ent->len = 1; + ent->tstamp = tstamp; +} + +int ngtcp2_acktr_entry_objalloc_new(ngtcp2_acktr_entry **ent, int64_t pkt_num, + ngtcp2_tstamp tstamp, + ngtcp2_objalloc *objalloc) { + *ent = ngtcp2_objalloc_acktr_entry_get(objalloc); + if (*ent == NULL) { + return NGTCP2_ERR_NOMEM; + } + + acktr_entry_init(*ent, pkt_num, tstamp); + + return 0; +} + +void ngtcp2_acktr_entry_objalloc_del(ngtcp2_acktr_entry *ent, + ngtcp2_objalloc *objalloc) { + ngtcp2_objalloc_acktr_entry_release(objalloc, ent); +} + +static int greater(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) { + return *(int64_t *)lhs > *(int64_t *)rhs; +} + +int ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log, + const ngtcp2_mem *mem) { + int rv; + + ngtcp2_objalloc_acktr_entry_init(&acktr->objalloc, 32, mem); + + rv = ngtcp2_ringbuf_init(&acktr->acks, 32, sizeof(ngtcp2_acktr_ack_entry), + mem); + if (rv != 0) { + return rv; + } + + ngtcp2_ksl_init(&acktr->ents, greater, sizeof(int64_t), mem); + + acktr->log = log; + acktr->mem = mem; + acktr->flags = NGTCP2_ACKTR_FLAG_NONE; + acktr->first_unacked_ts = UINT64_MAX; + acktr->rx_npkt = 0; + + return 0; +} + +void ngtcp2_acktr_free(ngtcp2_acktr *acktr) { +#ifdef NOMEMPOOL + ngtcp2_ksl_it it; +#endif /* NOMEMPOOL */ + + if (acktr == NULL) { + return; + } + +#ifdef NOMEMPOOL + for (it = ngtcp2_ksl_begin(&acktr->ents); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_acktr_entry_objalloc_del(ngtcp2_ksl_it_get(&it), &acktr->objalloc); + } +#endif /* NOMEMPOOL */ + + ngtcp2_ksl_free(&acktr->ents); + + ngtcp2_ringbuf_free(&acktr->acks); + + ngtcp2_objalloc_free(&acktr->objalloc); +} + +int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, + ngtcp2_tstamp ts) { + ngtcp2_ksl_it it, prev_it; + ngtcp2_acktr_entry *ent, *prev_ent, *delent; + int rv; + int added = 0; + + if (ngtcp2_ksl_len(&acktr->ents)) { + it = ngtcp2_ksl_lower_bound(&acktr->ents, &pkt_num); + if (ngtcp2_ksl_it_end(&it)) { + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + assert(ent->pkt_num >= pkt_num + (int64_t)ent->len); + + if (ent->pkt_num == pkt_num + (int64_t)ent->len) { + ++ent->len; + added = 1; + } + } else { + ent = ngtcp2_ksl_it_get(&it); + + assert(ent->pkt_num != pkt_num); + + if (ngtcp2_ksl_it_begin(&it)) { + if (ent->pkt_num + 1 == pkt_num) { + ngtcp2_ksl_update_key(&acktr->ents, &ent->pkt_num, &pkt_num); + ent->pkt_num = pkt_num; + ent->tstamp = ts; + ++ent->len; + added = 1; + } + } else { + prev_it = it; + ngtcp2_ksl_it_prev(&prev_it); + prev_ent = ngtcp2_ksl_it_get(&prev_it); + + assert(prev_ent->pkt_num >= pkt_num + (int64_t)prev_ent->len); + + if (ent->pkt_num + 1 == pkt_num) { + if (prev_ent->pkt_num == pkt_num + (int64_t)prev_ent->len) { + prev_ent->len += ent->len + 1; + ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &ent->pkt_num); + ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); + added = 1; + } else { + ngtcp2_ksl_update_key(&acktr->ents, &ent->pkt_num, &pkt_num); + ent->pkt_num = pkt_num; + ent->tstamp = ts; + ++ent->len; + added = 1; + } + } else if (prev_ent->pkt_num == pkt_num + (int64_t)prev_ent->len) { + ++prev_ent->len; + added = 1; + } + } + } + } + + if (!added) { + rv = ngtcp2_acktr_entry_objalloc_new(&ent, pkt_num, ts, &acktr->objalloc); + if (rv != 0) { + return rv; + } + rv = ngtcp2_ksl_insert(&acktr->ents, NULL, &ent->pkt_num, ent); + if (rv != 0) { + ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); + return rv; + } + } + + if (active_ack) { + acktr->flags |= NGTCP2_ACKTR_FLAG_ACTIVE_ACK; + if (acktr->first_unacked_ts == UINT64_MAX) { + acktr->first_unacked_ts = ts; + } + } + + if (ngtcp2_ksl_len(&acktr->ents) > NGTCP2_ACKTR_MAX_ENT) { + it = ngtcp2_ksl_end(&acktr->ents); + ngtcp2_ksl_it_prev(&it); + delent = ngtcp2_ksl_it_get(&it); + ngtcp2_ksl_remove_hint(&acktr->ents, NULL, &it, &delent->pkt_num); + ngtcp2_acktr_entry_objalloc_del(delent, &acktr->objalloc); + } + + return 0; +} + +void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent) { + ngtcp2_ksl_it it; + + it = ngtcp2_ksl_lower_bound(&acktr->ents, &ent->pkt_num); + assert(*(int64_t *)ngtcp2_ksl_it_key(&it) == (int64_t)ent->pkt_num); + + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + ngtcp2_ksl_remove_hint(&acktr->ents, &it, &it, &ent->pkt_num); + ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); + } +} + +ngtcp2_ksl_it ngtcp2_acktr_get(ngtcp2_acktr *acktr) { + return ngtcp2_ksl_begin(&acktr->ents); +} + +int ngtcp2_acktr_empty(ngtcp2_acktr *acktr) { + ngtcp2_ksl_it it = ngtcp2_ksl_begin(&acktr->ents); + return ngtcp2_ksl_it_end(&it); +} + +ngtcp2_acktr_ack_entry *ngtcp2_acktr_add_ack(ngtcp2_acktr *acktr, + int64_t pkt_num, + int64_t largest_ack) { + ngtcp2_acktr_ack_entry *ent = ngtcp2_ringbuf_push_front(&acktr->acks); + + ent->largest_ack = largest_ack; + ent->pkt_num = pkt_num; + + return ent; +} + +/* + * acktr_remove removes |ent| from |acktr|. |it| must point to the + * node whose key identifies |ent|. The iterator which points to the + * entry next to |ent| is assigned to |it|. + */ +static void acktr_remove(ngtcp2_acktr *acktr, ngtcp2_ksl_it *it, + ngtcp2_acktr_entry *ent) { + ngtcp2_ksl_remove_hint(&acktr->ents, it, it, &ent->pkt_num); + ngtcp2_acktr_entry_objalloc_del(ent, &acktr->objalloc); +} + +static void acktr_on_ack(ngtcp2_acktr *acktr, ngtcp2_ringbuf *rb, + size_t ack_ent_offset) { + ngtcp2_acktr_ack_entry *ack_ent; + ngtcp2_acktr_entry *ent; + ngtcp2_ksl_it it; + + assert(ngtcp2_ringbuf_len(rb)); + + ack_ent = ngtcp2_ringbuf_get(rb, ack_ent_offset); + + /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */ + it = ngtcp2_ksl_lower_bound(&acktr->ents, &ack_ent->largest_ack); + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + acktr_remove(acktr, &it, ent); + } + + if (ngtcp2_ksl_len(&acktr->ents)) { + assert(ngtcp2_ksl_it_end(&it)); + + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + if (ent->pkt_num > ack_ent->largest_ack && + ack_ent->largest_ack >= ent->pkt_num - (int64_t)(ent->len - 1)) { + ent->len = (size_t)(ent->pkt_num - ack_ent->largest_ack); + } + } + + ngtcp2_ringbuf_resize(rb, ack_ent_offset); +} + +void ngtcp2_acktr_recv_ack(ngtcp2_acktr *acktr, const ngtcp2_ack *fr) { + ngtcp2_acktr_ack_entry *ent; + int64_t largest_ack = fr->largest_ack, min_ack; + size_t i, j; + ngtcp2_ringbuf *rb = &acktr->acks; + size_t nacks = ngtcp2_ringbuf_len(rb); + + /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */ + for (j = 0; j < nacks; ++j) { + ent = ngtcp2_ringbuf_get(rb, j); + if (largest_ack >= ent->pkt_num) { + break; + } + } + if (j == nacks) { + return; + } + + min_ack = largest_ack - (int64_t)fr->first_ack_range; + + if (min_ack <= ent->pkt_num && ent->pkt_num <= largest_ack) { + acktr_on_ack(acktr, rb, j); + return; + } + + for (i = 0; i < fr->rangecnt && j < nacks; ++i) { + largest_ack = min_ack - (int64_t)fr->ranges[i].gap - 2; + min_ack = largest_ack - (int64_t)fr->ranges[i].len; + + for (;;) { + if (ent->pkt_num > largest_ack) { + ++j; + if (j == nacks) { + return; + } + ent = ngtcp2_ringbuf_get(rb, j); + continue; + } + if (ent->pkt_num < min_ack) { + break; + } + acktr_on_ack(acktr, rb, j); + return; + } + } +} + +void ngtcp2_acktr_commit_ack(ngtcp2_acktr *acktr) { + acktr->flags &= (uint16_t) ~(NGTCP2_ACKTR_FLAG_ACTIVE_ACK | + NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK | + NGTCP2_ACKTR_FLAG_CANCEL_TIMER); + acktr->first_unacked_ts = UINT64_MAX; + acktr->rx_npkt = 0; +} + +int ngtcp2_acktr_require_active_ack(ngtcp2_acktr *acktr, + ngtcp2_duration max_ack_delay, + ngtcp2_tstamp ts) { + return acktr->first_unacked_ts != UINT64_MAX && + acktr->first_unacked_ts + max_ack_delay <= ts; +} + +void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr) { + acktr->flags |= NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK; +} diff --git a/lib/ngtcp2_acktr.h b/lib/ngtcp2_acktr.h new file mode 100644 index 0000000..70a3c71 --- /dev/null +++ b/lib/ngtcp2_acktr.h @@ -0,0 +1,221 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_ACKTR_H +#define NGTCP2_ACKTR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_ringbuf.h" +#include "ngtcp2_ksl.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_objalloc.h" + +/* NGTCP2_ACKTR_MAX_ENT is the maximum number of ngtcp2_acktr_entry + which ngtcp2_acktr stores. */ +#define NGTCP2_ACKTR_MAX_ENT 1024 + +typedef struct ngtcp2_log ngtcp2_log; + +/* + * ngtcp2_acktr_entry is a range of packets which need to be acked. + */ +typedef struct ngtcp2_acktr_entry { + union { + struct { + /* pkt_num is the largest packet number to acknowledge in this + range. */ + int64_t pkt_num; + /* len is the consecutive packets started from pkt_num which + includes pkt_num itself counting in decreasing order. So pkt_num + = 987 and len = 2, this entry includes packet 987 and 986. */ + size_t len; + /* tstamp is the timestamp when a packet denoted by pkt_num is + received. */ + ngtcp2_tstamp tstamp; + }; + + ngtcp2_opl_entry oplent; + }; +} ngtcp2_acktr_entry; + +ngtcp2_objalloc_def(acktr_entry, ngtcp2_acktr_entry, oplent); + +/* + * ngtcp2_acktr_entry_objalloc_new allocates memory for ent, and + * initializes it with the given parameters. The pointer to the + * allocated object is stored to |*ent|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_acktr_entry_objalloc_new(ngtcp2_acktr_entry **ent, int64_t pkt_num, + ngtcp2_tstamp tstamp, + ngtcp2_objalloc *objalloc); + +/* + * ngtcp2_acktr_entry_objalloc_del deallocates memory allocated for + * |ent|. + */ +void ngtcp2_acktr_entry_objalloc_del(ngtcp2_acktr_entry *ent, + ngtcp2_objalloc *objalloc); + +typedef struct ngtcp2_acktr_ack_entry { + /* largest_ack is the largest packet number in outgoing ACK frame */ + int64_t largest_ack; + /* pkt_num is the packet number that ACK frame is included. */ + int64_t pkt_num; +} ngtcp2_acktr_ack_entry; + +/* NGTCP2_ACKTR_FLAG_NONE indicates that no flag set. */ +#define NGTCP2_ACKTR_FLAG_NONE 0x00u +/* NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK indicates that immediate + acknowledgement is required. */ +#define NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK 0x01u +/* NGTCP2_ACKTR_FLAG_ACTIVE_ACK indicates that there are pending + protected packet to be acknowledged. */ +#define NGTCP2_ACKTR_FLAG_ACTIVE_ACK 0x02u +/* NGTCP2_ACKTR_FLAG_CANCEL_TIMER is set when ACK delay timer is + expired and canceled. */ +#define NGTCP2_ACKTR_FLAG_CANCEL_TIMER 0x0100u + +/* + * ngtcp2_acktr tracks received packets which we have to send ack. + */ +typedef struct ngtcp2_acktr { + ngtcp2_objalloc objalloc; + ngtcp2_ringbuf acks; + /* ents includes ngtcp2_acktr_entry sorted by decreasing order of + packet number. */ + ngtcp2_ksl ents; + ngtcp2_log *log; + const ngtcp2_mem *mem; + /* flags is bitwise OR of zero, or more of NGTCP2_ACKTR_FLAG_*. */ + uint16_t flags; + /* first_unacked_ts is timestamp when ngtcp2_acktr_entry is added + first time after the last outgoing ACK frame. */ + ngtcp2_tstamp first_unacked_ts; + /* rx_npkt is the number of ACK eliciting packets received without + sending ACK. */ + size_t rx_npkt; +} ngtcp2_acktr; + +/* + * ngtcp2_acktr_init initializes |acktr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_acktr_init(ngtcp2_acktr *acktr, ngtcp2_log *log, + const ngtcp2_mem *mem); + +/* + * ngtcp2_acktr_free frees resources allocated for |acktr|. It frees + * any ngtcp2_acktr_entry added to |acktr|. + */ +void ngtcp2_acktr_free(ngtcp2_acktr *acktr); + +/* + * ngtcp2_acktr_add adds packet number |pkt_num| to |acktr|. + * |active_ack| is nonzero if |pkt_num| is retransmittable packet. + * + * This function assumes that |acktr| does not contain |pkt_num|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * OUt of memory. + */ +int ngtcp2_acktr_add(ngtcp2_acktr *acktr, int64_t pkt_num, int active_ack, + ngtcp2_tstamp ts); + +/* + * ngtcp2_acktr_forget removes all entries which have the packet + * number that is equal to or less than ent->pkt_num. This function + * assumes that |acktr| includes |ent|. + */ +void ngtcp2_acktr_forget(ngtcp2_acktr *acktr, ngtcp2_acktr_entry *ent); + +/* + * ngtcp2_acktr_get returns the pointer to pointer to the entry which + * has the largest packet number to be acked. If there is no entry, + * returned value satisfies ngtcp2_ksl_it_end(&it) != 0. + */ +ngtcp2_ksl_it ngtcp2_acktr_get(ngtcp2_acktr *acktr); + +/* + * ngtcp2_acktr_empty returns nonzero if it has no packet to + * acknowledge. + */ +int ngtcp2_acktr_empty(ngtcp2_acktr *acktr); + +/* + * ngtcp2_acktr_add_ack records outgoing ACK frame whose largest + * acknowledged packet number is |largest_ack|. |pkt_num| is the + * packet number of a packet in which ACK frame is included. This + * function returns a pointer to the object it adds. + */ +ngtcp2_acktr_ack_entry * +ngtcp2_acktr_add_ack(ngtcp2_acktr *acktr, int64_t pkt_num, int64_t largest_ack); + +/* + * ngtcp2_acktr_recv_ack processes the incoming ACK frame |fr|. + * |pkt_num| is a packet number which includes |fr|. If we receive + * ACK which acknowledges the ACKs added by ngtcp2_acktr_add_ack, + * ngtcp2_acktr_entry which the outgoing ACK acknowledges is removed. + */ +void ngtcp2_acktr_recv_ack(ngtcp2_acktr *acktr, const ngtcp2_ack *fr); + +/* + * ngtcp2_acktr_commit_ack tells |acktr| that ACK frame is generated. + */ +void ngtcp2_acktr_commit_ack(ngtcp2_acktr *acktr); + +/* + * ngtcp2_acktr_require_active_ack returns nonzero if ACK frame should + * be generated actively. + */ +int ngtcp2_acktr_require_active_ack(ngtcp2_acktr *acktr, + ngtcp2_duration max_ack_delay, + ngtcp2_tstamp ts); + +/* + * ngtcp2_acktr_immediate_ack tells |acktr| that immediate + * acknowledgement is required. + */ +void ngtcp2_acktr_immediate_ack(ngtcp2_acktr *acktr); + +#endif /* NGTCP2_ACKTR_H */ diff --git a/lib/ngtcp2_addr.c b/lib/ngtcp2_addr.c new file mode 100644 index 0000000..f389abe --- /dev/null +++ b/lib/ngtcp2_addr.c @@ -0,0 +1,117 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_addr.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_unreachable.h" + +ngtcp2_addr *ngtcp2_addr_init(ngtcp2_addr *dest, const ngtcp2_sockaddr *addr, + ngtcp2_socklen addrlen) { + dest->addrlen = addrlen; + dest->addr = (ngtcp2_sockaddr *)addr; + return dest; +} + +void ngtcp2_addr_copy(ngtcp2_addr *dest, const ngtcp2_addr *src) { + dest->addrlen = src->addrlen; + if (src->addrlen) { + memcpy(dest->addr, src->addr, (size_t)src->addrlen); + } +} + +void ngtcp2_addr_copy_byte(ngtcp2_addr *dest, const ngtcp2_sockaddr *addr, + ngtcp2_socklen addrlen) { + dest->addrlen = addrlen; + if (addrlen) { + memcpy(dest->addr, addr, (size_t)addrlen); + } +} + +static int sockaddr_eq(const ngtcp2_sockaddr *a, const ngtcp2_sockaddr *b) { + assert(a->sa_family == b->sa_family); + + switch (a->sa_family) { + case NGTCP2_AF_INET: { + const ngtcp2_sockaddr_in *ai = (const ngtcp2_sockaddr_in *)(void *)a, + *bi = (const ngtcp2_sockaddr_in *)(void *)b; + return ai->sin_port == bi->sin_port && + memcmp(&ai->sin_addr, &bi->sin_addr, sizeof(ai->sin_addr)) == 0; + } + case NGTCP2_AF_INET6: { + const ngtcp2_sockaddr_in6 *ai = (const ngtcp2_sockaddr_in6 *)(void *)a, + *bi = (const ngtcp2_sockaddr_in6 *)(void *)b; + return ai->sin6_port == bi->sin6_port && + memcmp(&ai->sin6_addr, &bi->sin6_addr, sizeof(ai->sin6_addr)) == 0; + } + default: + ngtcp2_unreachable(); + } +} + +int ngtcp2_addr_eq(const ngtcp2_addr *a, const ngtcp2_addr *b) { + return a->addr->sa_family == b->addr->sa_family && + sockaddr_eq(a->addr, b->addr); +} + +uint32_t ngtcp2_addr_compare(const ngtcp2_addr *aa, const ngtcp2_addr *bb) { + uint32_t flags = NGTCP2_ADDR_COMPARE_FLAG_NONE; + const ngtcp2_sockaddr *a = aa->addr; + const ngtcp2_sockaddr *b = bb->addr; + + if (a->sa_family != b->sa_family) { + return NGTCP2_ADDR_COMPARE_FLAG_FAMILY; + } + + switch (a->sa_family) { + case NGTCP2_AF_INET: { + const ngtcp2_sockaddr_in *ai = (const ngtcp2_sockaddr_in *)(void *)a, + *bi = (const ngtcp2_sockaddr_in *)(void *)b; + if (memcmp(&ai->sin_addr, &bi->sin_addr, sizeof(ai->sin_addr))) { + flags |= NGTCP2_ADDR_COMPARE_FLAG_ADDR; + } + if (ai->sin_port != bi->sin_port) { + flags |= NGTCP2_ADDR_COMPARE_FLAG_PORT; + } + return flags; + } + case NGTCP2_AF_INET6: { + const ngtcp2_sockaddr_in6 *ai = (const ngtcp2_sockaddr_in6 *)(void *)a, + *bi = (const ngtcp2_sockaddr_in6 *)(void *)b; + if (memcmp(&ai->sin6_addr, &bi->sin6_addr, sizeof(ai->sin6_addr))) { + flags |= NGTCP2_ADDR_COMPARE_FLAG_ADDR; + } + if (ai->sin6_port != bi->sin6_port) { + flags |= NGTCP2_ADDR_COMPARE_FLAG_PORT; + } + return flags; + } + default: + ngtcp2_unreachable(); + } +} + +int ngtcp2_addr_empty(const ngtcp2_addr *addr) { return addr->addrlen == 0; } diff --git a/lib/ngtcp2_addr.h b/lib/ngtcp2_addr.h new file mode 100644 index 0000000..f1d7f7b --- /dev/null +++ b/lib/ngtcp2_addr.h @@ -0,0 +1,69 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_ADDR_H +#define NGTCP2_ADDR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * ngtcp2_addr_copy copies |src| to |dest|. This function assumes + * that dest->addr points to a buffer which have sufficient size to + * store the copy. + */ +void ngtcp2_addr_copy(ngtcp2_addr *dest, const ngtcp2_addr *src); + +/* + * ngtcp2_addr_eq returns nonzero if |a| equals |b|. + */ +int ngtcp2_addr_eq(const ngtcp2_addr *a, const ngtcp2_addr *b); + +/* NGTCP2_ADDR_COMPARE_FLAG_NONE indicates that no flag set. */ +#define NGTCP2_ADDR_COMPARE_FLAG_NONE 0x0u +/* NGTCP2_ADDR_COMPARE_FLAG_ADDR indicates IP addresses do not + match. */ +#define NGTCP2_ADDR_COMPARE_FLAG_ADDR 0x1u +/* NGTCP2_ADDR_COMPARE_FLAG_PORT indicates ports do not match. */ +#define NGTCP2_ADDR_COMPARE_FLAG_PORT 0x2u +/* NGTCP2_ADDR_COMPARE_FLAG_FAMILY indicates address families do not + match. */ +#define NGTCP2_ADDR_COMPARE_FLAG_FAMILY 0x4u + +/* + * ngtcp2_addr_compare compares address and port between |a| and |b|, + * and returns zero or more of NGTCP2_ADDR_COMPARE_FLAG_*. + */ +uint32_t ngtcp2_addr_compare(const ngtcp2_addr *a, const ngtcp2_addr *b); + +/* + * ngtcp2_addr_empty returns nonzero if |addr| has zero length + * address. + */ +int ngtcp2_addr_empty(const ngtcp2_addr *addr); + +#endif /* NGTCP2_ADDR_H */ diff --git a/lib/ngtcp2_balloc.c b/lib/ngtcp2_balloc.c new file mode 100644 index 0000000..5cc39ee --- /dev/null +++ b/lib/ngtcp2_balloc.c @@ -0,0 +1,90 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_balloc.h" + +#include <assert.h> + +#include "ngtcp2_mem.h" + +void ngtcp2_balloc_init(ngtcp2_balloc *balloc, size_t blklen, + const ngtcp2_mem *mem) { + assert((blklen & 0xfu) == 0); + + balloc->mem = mem; + balloc->blklen = blklen; + balloc->head = NULL; + ngtcp2_buf_init(&balloc->buf, (void *)"", 0); +} + +void ngtcp2_balloc_free(ngtcp2_balloc *balloc) { + if (balloc == NULL) { + return; + } + + ngtcp2_balloc_clear(balloc); +} + +void ngtcp2_balloc_clear(ngtcp2_balloc *balloc) { + ngtcp2_memblock_hd *p, *next; + + for (p = balloc->head; p; p = next) { + next = p->next; + ngtcp2_mem_free(balloc->mem, p); + } + + balloc->head = NULL; + ngtcp2_buf_init(&balloc->buf, (void *)"", 0); +} + +int ngtcp2_balloc_get(ngtcp2_balloc *balloc, void **pbuf, size_t n) { + uint8_t *p; + ngtcp2_memblock_hd *hd; + + assert(n <= balloc->blklen); + + if (ngtcp2_buf_left(&balloc->buf) < n) { + p = ngtcp2_mem_malloc(balloc->mem, + sizeof(ngtcp2_memblock_hd) + 0x10u + balloc->blklen); + if (p == NULL) { + return NGTCP2_ERR_NOMEM; + } + + hd = (ngtcp2_memblock_hd *)(void *)p; + hd->next = balloc->head; + balloc->head = hd; + ngtcp2_buf_init( + &balloc->buf, + (uint8_t *)(((uintptr_t)p + sizeof(ngtcp2_memblock_hd) + 0xfu) & + ~(uintptr_t)0xfu), + balloc->blklen); + } + + assert(((uintptr_t)balloc->buf.last & 0xfu) == 0); + + *pbuf = balloc->buf.last; + balloc->buf.last += (n + 0xfu) & ~(uintptr_t)0xfu; + + return 0; +} diff --git a/lib/ngtcp2_balloc.h b/lib/ngtcp2_balloc.h new file mode 100644 index 0000000..1fb1632 --- /dev/null +++ b/lib/ngtcp2_balloc.h @@ -0,0 +1,91 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_BALLOC_H +#define NGTCP2_BALLOC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_buf.h" + +typedef struct ngtcp2_memblock_hd ngtcp2_memblock_hd; + +/* + * ngtcp2_memblock_hd is the header of memory block. + */ +struct ngtcp2_memblock_hd { + ngtcp2_memblock_hd *next; +}; + +/* + * ngtcp2_balloc is a custom memory allocator. It allocates |blklen| + * bytes of memory at once on demand, and returns its slice when the + * allocation is requested. + */ +typedef struct ngtcp2_balloc { + /* mem is the underlying memory allocator. */ + const ngtcp2_mem *mem; + /* blklen is the size of memory block. */ + size_t blklen; + /* head points to the list of memory block allocated so far. */ + ngtcp2_memblock_hd *head; + /* buf wraps the current memory block for allocation requests. */ + ngtcp2_buf buf; +} ngtcp2_balloc; + +/* + * ngtcp2_balloc_init initializes |balloc| with |blklen| which is the + * size of memory block. + */ +void ngtcp2_balloc_init(ngtcp2_balloc *balloc, size_t blklen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_balloc_free releases all allocated memory blocks. + */ +void ngtcp2_balloc_free(ngtcp2_balloc *balloc); + +/* + * ngtcp2_balloc_get allocates |n| bytes of memory and assigns its + * pointer to |*pbuf|. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_balloc_get(ngtcp2_balloc *balloc, void **pbuf, size_t n); + +/* + * ngtcp2_balloc_clear releases all allocated memory blocks and + * initializes its state. + */ +void ngtcp2_balloc_clear(ngtcp2_balloc *balloc); + +#endif /* NGTCP2_BALLOC_H */ diff --git a/lib/ngtcp2_bbr.c b/lib/ngtcp2_bbr.c new file mode 100644 index 0000000..ed26d3e --- /dev/null +++ b/lib/ngtcp2_bbr.c @@ -0,0 +1,692 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_bbr.h" + +#include <assert.h> + +#include "ngtcp2_log.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_rcvry.h" +#include "ngtcp2_rst.h" + +static const double pacing_gain_cycle[] = {1.25, 0.75, 1, 1, 1, 1, 1, 1}; + +#define NGTCP2_BBR_GAIN_CYCLELEN ngtcp2_arraylen(pacing_gain_cycle) + +#define NGTCP2_BBR_HIGH_GAIN 2.89 +#define NGTCP2_BBR_PROBE_RTT_DURATION (200 * NGTCP2_MILLISECONDS) +#define NGTCP2_RTPROP_FILTERLEN (10 * NGTCP2_SECONDS) +#define NGTCP2_BBR_BTL_BW_FILTERLEN 10 + +static void bbr_update_on_ack(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); +static void bbr_update_model_and_state(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); +static void bbr_update_control_parameters(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); +static void bbr_on_transmit(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat); +static void bbr_init_round_counting(ngtcp2_bbr_cc *cc); +static void bbr_update_round(ngtcp2_bbr_cc *cc, const ngtcp2_cc_ack *ack); +static void bbr_update_btl_bw(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); +static void bbr_update_rtprop(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); +static void bbr_init_pacing_rate(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat); +static void bbr_set_pacing_rate_with_gain(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat, + double pacing_gain); +static void bbr_set_pacing_rate(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat); +static void bbr_set_send_quantum(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat); +static void bbr_update_target_cwnd(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat); +static void bbr_modulate_cwnd_for_recovery(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); +static void bbr_save_cwnd(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat); +static void bbr_restore_cwnd(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat); +static void bbr_modulate_cwnd_for_probe_rtt(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat); +static void bbr_set_cwnd(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); +static void bbr_init(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp initial_ts); +static void bbr_enter_startup(ngtcp2_bbr_cc *cc); +static void bbr_init_full_pipe(ngtcp2_bbr_cc *cc); +static void bbr_check_full_pipe(ngtcp2_bbr_cc *cc); +static void bbr_enter_drain(ngtcp2_bbr_cc *cc); +static void bbr_check_drain(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); +static void bbr_enter_probe_bw(ngtcp2_bbr_cc *cc, ngtcp2_tstamp ts); +static void bbr_check_cycle_phase(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); +static void bbr_advance_cycle_phase(ngtcp2_bbr_cc *cc, ngtcp2_tstamp ts); +static int bbr_is_next_cycle_phase(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); +static void bbr_handle_restart_from_idle(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat); +static void bbr_check_probe_rtt(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); +static void bbr_enter_probe_rtt(ngtcp2_bbr_cc *cc); +static void bbr_handle_probe_rtt(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); +static void bbr_exit_probe_rtt(ngtcp2_bbr_cc *cc, ngtcp2_tstamp ts); + +void ngtcp2_bbr_cc_init(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_rst *rst, ngtcp2_tstamp initial_ts, + ngtcp2_rand rand, const ngtcp2_rand_ctx *rand_ctx, + ngtcp2_log *log) { + cc->ccb.log = log; + cc->rst = rst; + cc->rand = rand; + cc->rand_ctx = *rand_ctx; + cc->initial_cwnd = cstat->cwnd; + bbr_init(cc, cstat, initial_ts); +} + +void ngtcp2_bbr_cc_free(ngtcp2_bbr_cc *cc) { (void)cc; } + +int ngtcp2_cc_bbr_cc_init(ngtcp2_cc *cc, ngtcp2_log *log, + ngtcp2_conn_stat *cstat, ngtcp2_rst *rst, + ngtcp2_tstamp initial_ts, ngtcp2_rand rand, + const ngtcp2_rand_ctx *rand_ctx, + const ngtcp2_mem *mem) { + ngtcp2_bbr_cc *bbr_cc; + + bbr_cc = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_bbr_cc)); + if (bbr_cc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_bbr_cc_init(bbr_cc, cstat, rst, initial_ts, rand, rand_ctx, log); + + cc->ccb = &bbr_cc->ccb; + cc->on_pkt_acked = ngtcp2_cc_bbr_cc_on_pkt_acked; + cc->congestion_event = ngtcp2_cc_bbr_cc_congestion_event; + cc->on_spurious_congestion = ngtcp2_cc_bbr_cc_on_spurious_congestion; + cc->on_persistent_congestion = ngtcp2_cc_bbr_cc_on_persistent_congestion; + cc->on_ack_recv = ngtcp2_cc_bbr_cc_on_ack_recv; + cc->on_pkt_sent = ngtcp2_cc_bbr_cc_on_pkt_sent; + cc->new_rtt_sample = ngtcp2_cc_bbr_cc_new_rtt_sample; + cc->reset = ngtcp2_cc_bbr_cc_reset; + cc->event = ngtcp2_cc_bbr_cc_event; + + return 0; +} + +void ngtcp2_cc_bbr_cc_free(ngtcp2_cc *cc, const ngtcp2_mem *mem) { + ngtcp2_bbr_cc *bbr_cc = ngtcp2_struct_of(cc->ccb, ngtcp2_bbr_cc, ccb); + + ngtcp2_bbr_cc_free(bbr_cc); + ngtcp2_mem_free(mem, bbr_cc); +} + +void ngtcp2_cc_bbr_cc_on_pkt_acked(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { + (void)ccx; + (void)cstat; + (void)pkt; + (void)ts; +} + +static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_time) { + return cstat->congestion_recovery_start_ts != UINT64_MAX && + sent_time <= cstat->congestion_recovery_start_ts; +} + +void ngtcp2_cc_bbr_cc_congestion_event(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts) { + ngtcp2_bbr_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr_cc, ccb); + + if (cc->in_loss_recovery || cc->congestion_recovery_start_ts != UINT64_MAX || + in_congestion_recovery(cstat, sent_ts)) { + return; + } + + cc->congestion_recovery_start_ts = ts; +} + +void ngtcp2_cc_bbr_cc_on_spurious_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_bbr_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr_cc, ccb); + (void)ts; + + cc->congestion_recovery_start_ts = UINT64_MAX; + cstat->congestion_recovery_start_ts = UINT64_MAX; + + if (cc->in_loss_recovery) { + cc->in_loss_recovery = 0; + cc->packet_conservation = 0; + bbr_restore_cwnd(cc, cstat); + } +} + +void ngtcp2_cc_bbr_cc_on_persistent_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_bbr_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr_cc, ccb); + (void)ts; + + cstat->congestion_recovery_start_ts = UINT64_MAX; + cc->congestion_recovery_start_ts = UINT64_MAX; + cc->in_loss_recovery = 0; + cc->packet_conservation = 0; + + bbr_save_cwnd(cc, cstat); + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; +} + +void ngtcp2_cc_bbr_cc_on_ack_recv(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + ngtcp2_bbr_cc *bbr_cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr_cc, ccb); + + bbr_update_on_ack(bbr_cc, cstat, ack, ts); +} + +void ngtcp2_cc_bbr_cc_on_pkt_sent(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt) { + ngtcp2_bbr_cc *bbr_cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr_cc, ccb); + (void)pkt; + + bbr_on_transmit(bbr_cc, cstat); +} + +void ngtcp2_cc_bbr_cc_new_rtt_sample(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + (void)ccx; + (void)cstat; + (void)ts; +} + +void ngtcp2_cc_bbr_cc_reset(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_bbr_cc *bbr_cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr_cc, ccb); + bbr_init(bbr_cc, cstat, ts); +} + +void ngtcp2_cc_bbr_cc_event(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_cc_event_type event, ngtcp2_tstamp ts) { + (void)ccx; + (void)cstat; + (void)event; + (void)ts; +} + +static void bbr_update_on_ack(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + bbr_update_model_and_state(cc, cstat, ack, ts); + bbr_update_control_parameters(cc, cstat, ack); +} + +static void bbr_update_model_and_state(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + bbr_update_btl_bw(cc, cstat, ack); + bbr_check_cycle_phase(cc, cstat, ack, ts); + bbr_check_full_pipe(cc); + bbr_check_drain(cc, cstat, ts); + bbr_update_rtprop(cc, cstat, ts); + bbr_check_probe_rtt(cc, cstat, ts); +} + +static void bbr_update_control_parameters(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_set_pacing_rate(cc, cstat); + bbr_set_send_quantum(cc, cstat); + bbr_set_cwnd(cc, cstat, ack); +} + +static void bbr_on_transmit(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat) { + bbr_handle_restart_from_idle(cc, cstat); +} + +static void bbr_init_round_counting(ngtcp2_bbr_cc *cc) { + cc->next_round_delivered = 0; + cc->round_start = 0; + cc->round_count = 0; +} + +static void bbr_update_round(ngtcp2_bbr_cc *cc, const ngtcp2_cc_ack *ack) { + if (ack->pkt_delivered >= cc->next_round_delivered) { + cc->next_round_delivered = cc->rst->delivered; + ++cc->round_count; + cc->round_start = 1; + + return; + } + + cc->round_start = 0; +} + +static void bbr_handle_recovery(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + if (cc->in_loss_recovery) { + if (ack->pkt_delivered >= cc->congestion_recovery_next_round_delivered) { + cc->packet_conservation = 0; + } + + if (!in_congestion_recovery(cstat, ack->largest_acked_sent_ts)) { + cc->in_loss_recovery = 0; + cc->packet_conservation = 0; + bbr_restore_cwnd(cc, cstat); + } + + return; + } + + if (cc->congestion_recovery_start_ts != UINT64_MAX) { + cc->in_loss_recovery = 1; + bbr_save_cwnd(cc, cstat); + cstat->cwnd = + cstat->bytes_in_flight + + ngtcp2_max(ack->bytes_delivered, cstat->max_tx_udp_payload_size); + + cstat->congestion_recovery_start_ts = cc->congestion_recovery_start_ts; + cc->congestion_recovery_start_ts = UINT64_MAX; + cc->packet_conservation = 1; + cc->congestion_recovery_next_round_delivered = cc->rst->delivered; + } +} + +static void bbr_update_btl_bw(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_update_round(cc, ack); + bbr_handle_recovery(cc, cstat, ack); + + if (cstat->delivery_rate_sec < cc->btl_bw && cc->rst->rs.is_app_limited) { + return; + } + + ngtcp2_window_filter_update(&cc->btl_bw_filter, cstat->delivery_rate_sec, + cc->round_count); + + cc->btl_bw = ngtcp2_window_filter_get_best(&cc->btl_bw_filter); +} + +static void bbr_update_rtprop(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + cc->rtprop_expired = ts > cc->rtprop_stamp + NGTCP2_RTPROP_FILTERLEN; + + /* Need valid RTT sample */ + if (cstat->latest_rtt && + (cstat->latest_rtt <= cc->rt_prop || cc->rtprop_expired)) { + cc->rt_prop = cstat->latest_rtt; + cc->rtprop_stamp = ts; + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr update RTprop=%" PRIu64, cc->rt_prop); + } +} + +static void bbr_init_pacing_rate(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat) { + double nominal_bandwidth = + (double)cc->initial_cwnd / (double)NGTCP2_MILLISECONDS; + + cstat->pacing_rate = cc->pacing_gain * nominal_bandwidth; +} + +static void bbr_set_pacing_rate_with_gain(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat, + double pacing_gain) { + double rate = pacing_gain * (double)cc->btl_bw / NGTCP2_SECONDS; + + if (cc->filled_pipe || rate > cstat->pacing_rate) { + cstat->pacing_rate = rate; + } +} + +static void bbr_set_pacing_rate(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat) { + bbr_set_pacing_rate_with_gain(cc, cstat, cc->pacing_gain); +} + +static void bbr_set_send_quantum(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat) { + uint64_t send_quantum; + (void)cc; + + if (cstat->pacing_rate < 1.2 * 1024 * 1024 / 8 / NGTCP2_SECONDS) { + cstat->send_quantum = cstat->max_tx_udp_payload_size; + } else if (cstat->pacing_rate < 24.0 * 1024 * 1024 / 8 / NGTCP2_SECONDS) { + cstat->send_quantum = cstat->max_tx_udp_payload_size * 2; + } else { + send_quantum = + (uint64_t)(cstat->pacing_rate * (double)(cstat->min_rtt == UINT64_MAX + ? NGTCP2_MILLISECONDS + : cstat->min_rtt)); + cstat->send_quantum = (size_t)ngtcp2_min(send_quantum, 64 * 1024); + } + + cstat->send_quantum = + ngtcp2_max(cstat->send_quantum, cstat->max_tx_udp_payload_size * 10); +} + +static uint64_t bbr_inflight(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + double gain) { + uint64_t quanta = 3 * cstat->send_quantum; + double estimated_bdp; + + if (cc->rt_prop == UINT64_MAX) { + /* no valid RTT samples yet */ + return cc->initial_cwnd; + } + + estimated_bdp = (double)cc->btl_bw * (double)cc->rt_prop / NGTCP2_SECONDS; + + return (uint64_t)(gain * estimated_bdp) + quanta; +} + +static void bbr_update_target_cwnd(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat) { + cc->target_cwnd = bbr_inflight(cc, cstat, cc->cwnd_gain); +} + +static void bbr_modulate_cwnd_for_recovery(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + if (ack->bytes_lost > 0) { + if (cstat->cwnd > ack->bytes_lost) { + cstat->cwnd -= ack->bytes_lost; + cstat->cwnd = ngtcp2_max(cstat->cwnd, 2 * cstat->max_tx_udp_payload_size); + } else { + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; + } + } + + if (cc->packet_conservation) { + cstat->cwnd = + ngtcp2_max(cstat->cwnd, cstat->bytes_in_flight + ack->bytes_delivered); + } +} + +static void bbr_save_cwnd(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat) { + if (!cc->in_loss_recovery && cc->state != NGTCP2_BBR_STATE_PROBE_RTT) { + cc->prior_cwnd = cstat->cwnd; + return; + } + + cc->prior_cwnd = ngtcp2_max(cc->prior_cwnd, cstat->cwnd); +} + +static void bbr_restore_cwnd(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat) { + cstat->cwnd = ngtcp2_max(cstat->cwnd, cc->prior_cwnd); +} + +static uint64_t min_pipe_cwnd(size_t max_udp_payload_size) { + return max_udp_payload_size * 4; +} + +static void bbr_modulate_cwnd_for_probe_rtt(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat) { + if (cc->state == NGTCP2_BBR_STATE_PROBE_RTT) { + cstat->cwnd = + ngtcp2_min(cstat->cwnd, min_pipe_cwnd(cstat->max_tx_udp_payload_size)); + } +} + +static void bbr_set_cwnd(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_update_target_cwnd(cc, cstat); + bbr_modulate_cwnd_for_recovery(cc, cstat, ack); + + if (!cc->packet_conservation) { + if (cc->filled_pipe) { + cstat->cwnd = + ngtcp2_min(cstat->cwnd + ack->bytes_delivered, cc->target_cwnd); + } else if (cstat->cwnd < cc->target_cwnd || + cc->rst->delivered < cc->initial_cwnd) { + cstat->cwnd += ack->bytes_delivered; + } + + cstat->cwnd = + ngtcp2_max(cstat->cwnd, min_pipe_cwnd(cstat->max_tx_udp_payload_size)); + } + + bbr_modulate_cwnd_for_probe_rtt(cc, cstat); +} + +static void bbr_init(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp initial_ts) { + cc->pacing_gain = NGTCP2_BBR_HIGH_GAIN; + cc->prior_cwnd = 0; + cc->target_cwnd = 0; + cc->btl_bw = 0; + cc->rt_prop = UINT64_MAX; + cc->rtprop_stamp = initial_ts; + cc->cycle_stamp = UINT64_MAX; + cc->probe_rtt_done_stamp = UINT64_MAX; + cc->cycle_index = 0; + cc->rtprop_expired = 0; + cc->idle_restart = 0; + cc->packet_conservation = 0; + cc->probe_rtt_round_done = 0; + + cc->congestion_recovery_start_ts = UINT64_MAX; + cc->congestion_recovery_next_round_delivered = 0; + cc->in_loss_recovery = 0; + + cstat->send_quantum = cstat->max_tx_udp_payload_size * 10; + + ngtcp2_window_filter_init(&cc->btl_bw_filter, NGTCP2_BBR_BTL_BW_FILTERLEN); + + bbr_init_round_counting(cc); + bbr_init_full_pipe(cc); + bbr_init_pacing_rate(cc, cstat); + bbr_enter_startup(cc); +} + +static void bbr_enter_startup(ngtcp2_bbr_cc *cc) { + cc->state = NGTCP2_BBR_STATE_STARTUP; + cc->pacing_gain = NGTCP2_BBR_HIGH_GAIN; + cc->cwnd_gain = NGTCP2_BBR_HIGH_GAIN; +} + +static void bbr_init_full_pipe(ngtcp2_bbr_cc *cc) { + cc->filled_pipe = 0; + cc->full_bw = 0; + cc->full_bw_count = 0; +} + +static void bbr_check_full_pipe(ngtcp2_bbr_cc *cc) { + if (cc->filled_pipe || !cc->round_start || cc->rst->rs.is_app_limited) { + /* no need to check for a full pipe now. */ + return; + } + + /* cc->btl_bw still growing? */ + if (cc->btl_bw * 100 >= cc->full_bw * 125) { + /* record new baseline level */ + cc->full_bw = cc->btl_bw; + cc->full_bw_count = 0; + return; + } + /* another round w/o much growth */ + ++cc->full_bw_count; + if (cc->full_bw_count >= 3) { + cc->filled_pipe = 1; + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr filled pipe, btl_bw=%" PRIu64, cc->btl_bw); + } +} + +static void bbr_enter_drain(ngtcp2_bbr_cc *cc) { + cc->state = NGTCP2_BBR_STATE_DRAIN; + /* pace slowly */ + cc->pacing_gain = 1.0 / NGTCP2_BBR_HIGH_GAIN; + /* maintain cwnd */ + cc->cwnd_gain = NGTCP2_BBR_HIGH_GAIN; +} + +static void bbr_check_drain(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (cc->state == NGTCP2_BBR_STATE_STARTUP && cc->filled_pipe) { + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr exit Startup and enter Drain"); + + bbr_enter_drain(cc); + } + + if (cc->state == NGTCP2_BBR_STATE_DRAIN && + cstat->bytes_in_flight <= bbr_inflight(cc, cstat, 1.0)) { + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr exit Drain and enter ProbeBW"); + + /* we estimate queue is drained */ + bbr_enter_probe_bw(cc, ts); + } +} + +static void bbr_enter_probe_bw(ngtcp2_bbr_cc *cc, ngtcp2_tstamp ts) { + uint8_t rand; + + cc->state = NGTCP2_BBR_STATE_PROBE_BW; + cc->pacing_gain = 1; + cc->cwnd_gain = 2; + + assert(cc->rand); + + cc->rand(&rand, 1, &cc->rand_ctx); + + cc->cycle_index = NGTCP2_BBR_GAIN_CYCLELEN - 1 - (size_t)(rand * 7 / 256); + bbr_advance_cycle_phase(cc, ts); +} + +static void bbr_check_cycle_phase(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + if (cc->state == NGTCP2_BBR_STATE_PROBE_BW && + bbr_is_next_cycle_phase(cc, cstat, ack, ts)) { + bbr_advance_cycle_phase(cc, ts); + } +} + +static void bbr_advance_cycle_phase(ngtcp2_bbr_cc *cc, ngtcp2_tstamp ts) { + cc->cycle_stamp = ts; + cc->cycle_index = (cc->cycle_index + 1) & (NGTCP2_BBR_GAIN_CYCLELEN - 1); + cc->pacing_gain = pacing_gain_cycle[cc->cycle_index]; +} + +static int bbr_is_next_cycle_phase(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + int is_full_length = (ts - cc->cycle_stamp) > cc->rt_prop; + + if (cc->pacing_gain > 1) { + return is_full_length && (ack->bytes_lost > 0 || + ack->prior_bytes_in_flight >= + bbr_inflight(cc, cstat, cc->pacing_gain)); + } + + if (cc->pacing_gain < 1) { + return is_full_length || + ack->prior_bytes_in_flight <= bbr_inflight(cc, cstat, 1); + } + + return is_full_length; +} + +static void bbr_handle_restart_from_idle(ngtcp2_bbr_cc *cc, + ngtcp2_conn_stat *cstat) { + if (cstat->bytes_in_flight == 0 && cc->rst->app_limited) { + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, "bbr restart from idle"); + + cc->idle_restart = 1; + + if (cc->state == NGTCP2_BBR_STATE_PROBE_BW) { + bbr_set_pacing_rate_with_gain(cc, cstat, 1); + } + } +} + +static void bbr_check_probe_rtt(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (cc->state != NGTCP2_BBR_STATE_PROBE_RTT && cc->rtprop_expired && + !cc->idle_restart) { + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, "bbr enter ProbeRTT"); + + bbr_enter_probe_rtt(cc); + bbr_save_cwnd(cc, cstat); + cc->probe_rtt_done_stamp = UINT64_MAX; + } + + if (cc->state == NGTCP2_BBR_STATE_PROBE_RTT) { + bbr_handle_probe_rtt(cc, cstat, ts); + } + + cc->idle_restart = 0; +} + +static void bbr_enter_probe_rtt(ngtcp2_bbr_cc *cc) { + cc->state = NGTCP2_BBR_STATE_PROBE_RTT; + cc->pacing_gain = 1; + cc->cwnd_gain = 1; +} + +static void bbr_handle_probe_rtt(ngtcp2_bbr_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + uint64_t app_limited = cc->rst->delivered + cstat->bytes_in_flight; + + /* Ignore low rate samples during NGTCP2_BBR_STATE_PROBE_RTT. */ + cc->rst->app_limited = app_limited ? app_limited : 1; + + if (cc->probe_rtt_done_stamp == UINT64_MAX && + cstat->bytes_in_flight <= min_pipe_cwnd(cstat->max_tx_udp_payload_size)) { + cc->probe_rtt_done_stamp = ts + NGTCP2_BBR_PROBE_RTT_DURATION; + cc->probe_rtt_round_done = 0; + cc->next_round_delivered = cc->rst->delivered; + + return; + } + + if (cc->probe_rtt_done_stamp != UINT64_MAX) { + if (cc->round_start) { + cc->probe_rtt_round_done = 1; + } + + if (cc->probe_rtt_round_done && ts > cc->probe_rtt_done_stamp) { + cc->rtprop_stamp = ts; + bbr_restore_cwnd(cc, cstat); + bbr_exit_probe_rtt(cc, ts); + } + } +} + +static void bbr_exit_probe_rtt(ngtcp2_bbr_cc *cc, ngtcp2_tstamp ts) { + if (cc->filled_pipe) { + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr exit ProbeRTT and enter ProbeBW"); + + bbr_enter_probe_bw(cc, ts); + + return; + } + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr exit ProbeRTT and enter Startup"); + + bbr_enter_startup(cc); +} diff --git a/lib/ngtcp2_bbr.h b/lib/ngtcp2_bbr.h new file mode 100644 index 0000000..7311f05 --- /dev/null +++ b/lib/ngtcp2_bbr.h @@ -0,0 +1,156 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_BBR_H +#define NGTCP2_BBR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_cc.h" +#include "ngtcp2_window_filter.h" + +typedef struct ngtcp2_rst ngtcp2_rst; + +typedef enum ngtcp2_bbr_state { + NGTCP2_BBR_STATE_STARTUP, + NGTCP2_BBR_STATE_DRAIN, + NGTCP2_BBR_STATE_PROBE_BW, + NGTCP2_BBR_STATE_PROBE_RTT, +} ngtcp2_bbr_state; + +/* + * ngtcp2_bbr_cc is BBR congestion controller, described in + * https://tools.ietf.org/html/draft-cardwell-iccrg-bbr-congestion-control-00 + */ +typedef struct ngtcp2_bbr_cc { + ngtcp2_cc_base ccb; + + /* The max filter used to estimate BBR.BtlBw. */ + ngtcp2_window_filter btl_bw_filter; + uint64_t initial_cwnd; + ngtcp2_rst *rst; + ngtcp2_rand rand; + ngtcp2_rand_ctx rand_ctx; + + /* BBR variables */ + + /* The dynamic gain factor used to scale BBR.BtlBw to + produce BBR.pacing_rate. */ + double pacing_gain; + /* The dynamic gain factor used to scale the estimated BDP to produce a + congestion window (cwnd). */ + double cwnd_gain; + uint64_t full_bw; + /* packet.delivered value denoting the end of a packet-timed round trip. */ + uint64_t next_round_delivered; + /* Count of packet-timed round trips. */ + uint64_t round_count; + uint64_t prior_cwnd; + /* target_cwnd is the upper bound on the volume of data BBR + allows in flight. */ + uint64_t target_cwnd; + /* BBR's estimated bottleneck bandwidth available to the + transport flow, estimated from the maximum delivery rate sample in a + sliding window. */ + uint64_t btl_bw; + /* BBR's estimated two-way round-trip propagation delay of + the path, estimated from the windowed minimum recent round-trip delay + sample. */ + ngtcp2_duration rt_prop; + /* The wall clock time at which the current BBR.RTProp + sample was obtained. */ + ngtcp2_tstamp rtprop_stamp; + ngtcp2_tstamp cycle_stamp; + ngtcp2_tstamp probe_rtt_done_stamp; + /* congestion_recovery_start_ts is the time when congestion recovery + period started.*/ + ngtcp2_tstamp congestion_recovery_start_ts; + uint64_t congestion_recovery_next_round_delivered; + size_t full_bw_count; + size_t cycle_index; + ngtcp2_bbr_state state; + /* A boolean that records whether BBR estimates that it has ever fully + utilized its available bandwidth ("filled the pipe"). */ + int filled_pipe; + /* A boolean that BBR sets to true once per packet-timed round trip, + on ACKs that advance BBR.round_count. */ + int round_start; + int rtprop_expired; + int idle_restart; + int packet_conservation; + int probe_rtt_round_done; + /* in_loss_recovery becomes nonzero when BBR enters loss recovery + period. */ + int in_loss_recovery; +} ngtcp2_bbr_cc; + +int ngtcp2_cc_bbr_cc_init(ngtcp2_cc *cc, ngtcp2_log *log, + ngtcp2_conn_stat *cstat, ngtcp2_rst *rst, + ngtcp2_tstamp initial_ts, ngtcp2_rand rand, + const ngtcp2_rand_ctx *rand_ctx, + const ngtcp2_mem *mem); + +void ngtcp2_cc_bbr_cc_free(ngtcp2_cc *cc, const ngtcp2_mem *mem); + +void ngtcp2_bbr_cc_init(ngtcp2_bbr_cc *bbr_cc, ngtcp2_conn_stat *cstat, + ngtcp2_rst *rst, ngtcp2_tstamp initial_ts, + ngtcp2_rand rand, const ngtcp2_rand_ctx *rand_ctx, + ngtcp2_log *log); + +void ngtcp2_bbr_cc_free(ngtcp2_bbr_cc *cc); + +void ngtcp2_cc_bbr_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); + +void ngtcp2_cc_bbr_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, ngtcp2_tstamp ts); + +void ngtcp2_cc_bbr_cc_on_spurious_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_bbr_cc_on_persistent_congestion(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_bbr_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); + +void ngtcp2_cc_bbr_cc_on_pkt_sent(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt); + +void ngtcp2_cc_bbr_cc_new_rtt_sample(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_bbr_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_bbr_cc_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_cc_event_type event, ngtcp2_tstamp ts); + +#endif /* NGTCP2_BBR_H */ diff --git a/lib/ngtcp2_bbr2.c b/lib/ngtcp2_bbr2.c new file mode 100644 index 0000000..508ab55 --- /dev/null +++ b/lib/ngtcp2_bbr2.c @@ -0,0 +1,1489 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_bbr2.h" + +#include <assert.h> + +#include "ngtcp2_log.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_rcvry.h" +#include "ngtcp2_rst.h" + +#define NGTCP2_BBR_MAX_BW_FILTERLEN 2 + +#define NGTCP2_BBR_EXTRA_ACKED_FILTERLEN 10 + +#define NGTCP2_BBR_STARTUP_PACING_GAIN ((double)2.77) + +#define NGTCP2_BBR_STARTUP_CWND_GAIN 2 + +#define NGTCP2_BBR_PROBE_RTT_CWND_GAIN ((double)0.5) + +#define NGTCP2_BBR_BETA_NUMER 7 +#define NGTCP2_BBR_BETA_DENOM 10 + +#define NGTCP2_BBR_LOSS_THRESH_NUMER 2 +#define NGTCP2_BBR_LOSS_THRESH_DENOM 100 + +#define NGTCP2_BBR_HEADROOM_NUMER 15 +#define NGTCP2_BBR_HEADROOM_DENOM 100 + +#define NGTCP2_BBR_PROBE_RTT_INTERVAL (5 * NGTCP2_SECONDS) +#define NGTCP2_BBR_MIN_RTT_FILTERLEN (10 * NGTCP2_SECONDS) + +#define NGTCP2_BBR_PROBE_RTT_DURATION (200 * NGTCP2_MILLISECONDS) + +#define NGTCP2_BBR_PACING_MARGIN_PERCENT 1 + +static void bbr_on_init(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp initial_ts); + +static void bbr_on_transmit(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static void bbr_reset_congestion_signals(ngtcp2_bbr2_cc *bbr); + +static void bbr_reset_lower_bounds(ngtcp2_bbr2_cc *bbr); + +static void bbr_init_round_counting(ngtcp2_bbr2_cc *bbr); + +static void bbr_init_full_pipe(ngtcp2_bbr2_cc *bbr); + +static void bbr_init_pacing_rate(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat); + +static void bbr_set_pacing_rate_with_gain(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + double pacing_gain); + +static void bbr_set_pacing_rate(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat); + +static void bbr_enter_startup(ngtcp2_bbr2_cc *bbr); + +static void bbr_check_startup_done(ngtcp2_bbr2_cc *bbr, + const ngtcp2_cc_ack *ack); + +static void bbr_update_on_ack(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); + +static void bbr_update_model_and_state(ngtcp2_bbr2_cc *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +static void bbr_update_control_parameters(ngtcp2_bbr2_cc *cc, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +static void bbr_update_on_loss(ngtcp2_bbr2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); + +static void bbr_update_latest_delivery_signals(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_advance_latest_delivery_signals(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_update_congestion_signals(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_init_lower_bounds(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat); + +static void bbr_loss_lower_bounds(ngtcp2_bbr2_cc *bbr); + +static void bbr_bound_bw_for_model(ngtcp2_bbr2_cc *bbr); + +static void bbr_update_max_bw(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +static void bbr_update_round(ngtcp2_bbr2_cc *bbr, const ngtcp2_cc_ack *ack); + +static void bbr_start_round(ngtcp2_bbr2_cc *bbr); + +static int bbr_is_in_probe_bw_state(ngtcp2_bbr2_cc *bbr); + +static void bbr_update_ack_aggregation(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +static void bbr_enter_drain(ngtcp2_bbr2_cc *bbr); + +static void bbr_check_drain(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static void bbr_enter_probe_bw(ngtcp2_bbr2_cc *bbr, ngtcp2_tstamp ts); + +static void bbr_start_probe_bw_down(ngtcp2_bbr2_cc *bbr, ngtcp2_tstamp ts); + +static void bbr_start_probe_bw_cruise(ngtcp2_bbr2_cc *bbr); + +static void bbr_start_probe_bw_refill(ngtcp2_bbr2_cc *bbr); + +static void bbr_start_probe_bw_up(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static void bbr_update_probe_bw_cycle_phase(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +static int bbr_check_time_to_cruise(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); + +static int bbr_has_elapsed_in_phase(ngtcp2_bbr2_cc *bbr, + ngtcp2_duration interval, ngtcp2_tstamp ts); + +static uint64_t bbr_inflight_with_headroom(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_raise_inflight_hi_slope(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_probe_inflight_hi_upward(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +static void bbr_adapt_upper_bounds(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); + +static int bbr_check_time_to_probe_bw(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static void bbr_pick_probe_wait(ngtcp2_bbr2_cc *bbr); + +static int bbr_is_reno_coexistence_probe_time(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static uint64_t bbr_target_inflight(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static int bbr_check_inflight_too_high(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static int is_inflight_too_high(const ngtcp2_rs *rs); + +static void bbr_handle_inflight_too_high(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_rs *rs, ngtcp2_tstamp ts); + +static void bbr_handle_lost_packet(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); + +static uint64_t bbr_inflight_hi_from_lost_packet(ngtcp2_bbr2_cc *bbr, + const ngtcp2_rs *rs, + const ngtcp2_cc_pkt *pkt); + +static void bbr_update_min_rtt(ngtcp2_bbr2_cc *bbr, const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +static void bbr_check_probe_rtt(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static void bbr_enter_probe_rtt(ngtcp2_bbr2_cc *bbr); + +static void bbr_handle_probe_rtt(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static void bbr_check_probe_rtt_done(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); + +static void bbr_mark_connection_app_limited(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_exit_probe_rtt(ngtcp2_bbr2_cc *bbr, ngtcp2_tstamp ts); + +static void bbr_handle_restart_from_idle(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +static uint64_t bbr_bdp_multiple(ngtcp2_bbr2_cc *bbr, uint64_t bw, double gain); + +static uint64_t bbr_quantization_budget(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + uint64_t inflight); + +static uint64_t bbr_inflight(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + uint64_t bw, double gain); + +static void bbr_update_max_inflight(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_update_offload_budget(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static uint64_t min_pipe_cwnd(size_t max_udp_payload_size); + +static void bbr_advance_max_bw_filter(ngtcp2_bbr2_cc *bbr); + +static void bbr_modulate_cwnd_for_recovery(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +static void bbr_save_cwnd(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat); + +static void bbr_restore_cwnd(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat); + +static uint64_t bbr_probe_rtt_cwnd(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_set_cwnd(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +static void bbr_bound_cwnd_for_model(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat); + +static void bbr_set_send_quantum(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat); + +static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_time); + +static void bbr_handle_recovery(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack); + +static void bbr_on_init(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp initial_ts) { + ngtcp2_window_filter_init(&bbr->max_bw_filter, NGTCP2_BBR_MAX_BW_FILTERLEN); + ngtcp2_window_filter_init(&bbr->extra_acked_filter, + NGTCP2_BBR_EXTRA_ACKED_FILTERLEN); + + bbr->min_rtt = UINT64_MAX; + bbr->min_rtt_stamp = initial_ts; + /* remark: Use UINT64_MAX instead of 0 for consistency. */ + bbr->probe_rtt_done_stamp = UINT64_MAX; + bbr->probe_rtt_round_done = 0; + bbr->prior_cwnd = 0; + bbr->idle_restart = 0; + bbr->extra_acked_interval_start = initial_ts; + bbr->extra_acked_delivered = 0; + + bbr_reset_congestion_signals(bbr); + bbr_reset_lower_bounds(bbr); + bbr_init_round_counting(bbr); + bbr_init_full_pipe(bbr); + bbr_init_pacing_rate(bbr, cstat); + bbr_enter_startup(bbr); + + cstat->send_quantum = cstat->max_tx_udp_payload_size * 10; + + /* Missing in documentation */ + bbr->loss_round_start = 0; + bbr->loss_round_delivered = UINT64_MAX; + + bbr->rounds_since_bw_probe = 0; + + bbr->max_bw = 0; + bbr->bw = 0; + + bbr->cycle_count = 0; + + bbr->extra_acked = 0; + + bbr->bytes_lost_in_round = 0; + bbr->loss_events_in_round = 0; + + bbr->offload_budget = 0; + + bbr->probe_up_cnt = UINT64_MAX; + bbr->cycle_stamp = UINT64_MAX; + bbr->ack_phase = 0; + bbr->bw_probe_wait = 0; + bbr->bw_probe_samples = 0; + bbr->bw_probe_up_rounds = 0; + bbr->bw_probe_up_acks = 0; + + bbr->inflight_hi = UINT64_MAX; + bbr->bw_hi = UINT64_MAX; + + bbr->probe_rtt_expired = 0; + bbr->probe_rtt_min_delay = UINT64_MAX; + bbr->probe_rtt_min_stamp = initial_ts; + + bbr->in_loss_recovery = 0; + bbr->packet_conservation = 0; + + bbr->max_inflight = 0; + + bbr->congestion_recovery_start_ts = UINT64_MAX; + bbr->congestion_recovery_next_round_delivered = 0; + + bbr->prior_inflight_lo = 0; + bbr->prior_inflight_hi = 0; + bbr->prior_bw_lo = 0; +} + +static void bbr_reset_congestion_signals(ngtcp2_bbr2_cc *bbr) { + bbr->loss_in_round = 0; + bbr->bw_latest = 0; + bbr->inflight_latest = 0; +} + +static void bbr_reset_lower_bounds(ngtcp2_bbr2_cc *bbr) { + bbr->bw_lo = UINT64_MAX; + bbr->inflight_lo = UINT64_MAX; +} + +static void bbr_init_round_counting(ngtcp2_bbr2_cc *bbr) { + bbr->next_round_delivered = 0; + bbr->round_start = 0; + bbr->round_count = 0; +} + +static void bbr_init_full_pipe(ngtcp2_bbr2_cc *bbr) { + bbr->filled_pipe = 0; + bbr->full_bw = 0; + bbr->full_bw_count = 0; +} + +static void bbr_check_startup_full_bandwidth(ngtcp2_bbr2_cc *bbr) { + if (bbr->filled_pipe || !bbr->round_start || bbr->rst->rs.is_app_limited) { + return; + } + + if (bbr->max_bw * 100 >= bbr->full_bw * 125) { + bbr->full_bw = bbr->max_bw; + bbr->full_bw_count = 0; + } + + ++bbr->full_bw_count; + + if (bbr->full_bw_count >= 3) { + bbr->filled_pipe = 1; + + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr2 filled pipe, full_bw=%" PRIu64, bbr->full_bw); + } +} + +static void bbr_check_startup_high_loss(ngtcp2_bbr2_cc *bbr, + const ngtcp2_cc_ack *ack) { + if (bbr->filled_pipe || !bbr->round_start || bbr->rst->rs.is_app_limited) { + return; + } + + if (bbr->loss_events_in_round <= 3) { + return; + } + + /* loss_thresh = 2% */ + if (bbr->bytes_lost_in_round * 100 <= ack->prior_bytes_in_flight * 2) { + return; + } + + bbr->filled_pipe = 1; +} + +static void bbr_init_pacing_rate(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat) { + double nominal_bandwidth = (double)bbr->initial_cwnd; + + cstat->pacing_rate = NGTCP2_BBR_STARTUP_PACING_GAIN * nominal_bandwidth / + (double)NGTCP2_MILLISECONDS; +} + +static void bbr_set_pacing_rate_with_gain(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + double pacing_gain) { + double rate = pacing_gain * (double)bbr->bw * + (100 - NGTCP2_BBR_PACING_MARGIN_PERCENT) / 100 / NGTCP2_SECONDS; + + if (bbr->filled_pipe || rate > cstat->pacing_rate) { + cstat->pacing_rate = rate; + } +} + +static void bbr_set_pacing_rate(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat) { + bbr_set_pacing_rate_with_gain(bbr, cstat, bbr->pacing_gain); +} + +static void bbr_enter_startup(ngtcp2_bbr2_cc *bbr) { + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, "bbr2 enter Startup"); + + bbr->state = NGTCP2_BBR2_STATE_STARTUP; + bbr->pacing_gain = NGTCP2_BBR_STARTUP_PACING_GAIN; + bbr->cwnd_gain = NGTCP2_BBR_STARTUP_CWND_GAIN; +} + +static void bbr_check_startup_done(ngtcp2_bbr2_cc *bbr, + const ngtcp2_cc_ack *ack) { + bbr_check_startup_full_bandwidth(bbr); + bbr_check_startup_high_loss(bbr, ack); + + if (bbr->state == NGTCP2_BBR2_STATE_STARTUP && bbr->filled_pipe) { + bbr_enter_drain(bbr); + } +} + +static void bbr_on_transmit(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + bbr_handle_restart_from_idle(bbr, cstat, ts); +} + +static void bbr_update_on_ack(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + bbr_update_model_and_state(bbr, cstat, ack, ts); + bbr_update_control_parameters(bbr, cstat, ack); +} + +static void bbr_update_model_and_state(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + bbr_update_latest_delivery_signals(bbr, cstat); + bbr_update_congestion_signals(bbr, cstat, ack); + bbr_update_ack_aggregation(bbr, cstat, ack, ts); + bbr_check_startup_done(bbr, ack); + bbr_check_drain(bbr, cstat, ts); + bbr_update_probe_bw_cycle_phase(bbr, cstat, ack, ts); + bbr_update_min_rtt(bbr, ack, ts); + bbr_check_probe_rtt(bbr, cstat, ts); + bbr_advance_latest_delivery_signals(bbr, cstat); + bbr_bound_bw_for_model(bbr); +} + +static void bbr_update_control_parameters(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_set_pacing_rate(bbr, cstat); + bbr_set_send_quantum(bbr, cstat); + bbr_set_cwnd(bbr, cstat, ack); +} + +static void bbr_update_on_loss(ngtcp2_bbr2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { + bbr_handle_lost_packet(cc, cstat, pkt, ts); +} + +static void bbr_update_latest_delivery_signals(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + bbr->loss_round_start = 0; + bbr->bw_latest = ngtcp2_max(bbr->bw_latest, cstat->delivery_rate_sec); + bbr->inflight_latest = + ngtcp2_max(bbr->inflight_latest, bbr->rst->rs.delivered); + + if (bbr->rst->rs.prior_delivered >= bbr->loss_round_delivered) { + bbr->loss_round_delivered = bbr->rst->delivered; + bbr->loss_round_start = 1; + } +} + +static void bbr_advance_latest_delivery_signals(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + if (bbr->loss_round_start) { + bbr->bw_latest = cstat->delivery_rate_sec; + bbr->inflight_latest = bbr->rst->rs.delivered; + } +} + +static void bbr_update_congestion_signals(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_update_max_bw(bbr, cstat, ack); + + if (ack->bytes_lost) { + bbr->bytes_lost_in_round += ack->bytes_lost; + ++bbr->loss_events_in_round; + + if (!bbr->loss_in_round) { + bbr->loss_in_round = 1; + bbr->loss_round_delivered = bbr->rst->delivered; + } + } + + if (!bbr->loss_round_start) { + return; + } + + bbr_adapt_lower_bounds_from_congestion(bbr, cstat); + + bbr->loss_in_round = 0; +} + +static void bbr_adapt_lower_bounds_from_congestion(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + if (!bbr->filled_pipe || bbr_is_in_probe_bw_state(bbr)) { + return; + } + + if (bbr->loss_in_round) { + bbr_init_lower_bounds(bbr, cstat); + bbr_loss_lower_bounds(bbr); + } +} + +static void bbr_init_lower_bounds(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + if (bbr->bw_lo == UINT64_MAX) { + bbr->bw_lo = bbr->max_bw; + } + + if (bbr->inflight_lo == UINT64_MAX) { + bbr->inflight_lo = cstat->cwnd; + } +} + +static void bbr_loss_lower_bounds(ngtcp2_bbr2_cc *bbr) { + bbr->bw_lo = ngtcp2_max(bbr->bw_latest, bbr->bw_lo * NGTCP2_BBR_BETA_NUMER / + NGTCP2_BBR_BETA_DENOM); + bbr->inflight_lo = ngtcp2_max(bbr->inflight_latest, + bbr->inflight_lo * NGTCP2_BBR_BETA_NUMER / + NGTCP2_BBR_BETA_DENOM); +} + +static void bbr_bound_bw_for_model(ngtcp2_bbr2_cc *bbr) { + bbr->bw = ngtcp2_min(bbr->max_bw, bbr->bw_lo); + bbr->bw = ngtcp2_min(bbr->bw, bbr->bw_hi); +} + +static void bbr_update_max_bw(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + bbr_update_round(bbr, ack); + bbr_handle_recovery(bbr, cstat, ack); + + if (cstat->delivery_rate_sec >= bbr->max_bw || !bbr->rst->rs.is_app_limited) { + ngtcp2_window_filter_update(&bbr->max_bw_filter, cstat->delivery_rate_sec, + bbr->cycle_count); + + bbr->max_bw = ngtcp2_window_filter_get_best(&bbr->max_bw_filter); + } +} + +static void bbr_update_round(ngtcp2_bbr2_cc *bbr, const ngtcp2_cc_ack *ack) { + if (ack->pkt_delivered >= bbr->next_round_delivered) { + bbr_start_round(bbr); + + ++bbr->round_count; + ++bbr->rounds_since_bw_probe; + bbr->round_start = 1; + + bbr->bytes_lost_in_round = 0; + bbr->loss_events_in_round = 0; + + bbr->rst->is_cwnd_limited = 0; + + return; + } + + bbr->round_start = 0; +} + +static void bbr_start_round(ngtcp2_bbr2_cc *bbr) { + bbr->next_round_delivered = bbr->rst->delivered; +} + +static int bbr_is_in_probe_bw_state(ngtcp2_bbr2_cc *bbr) { + switch (bbr->state) { + case NGTCP2_BBR2_STATE_PROBE_BW_DOWN: + case NGTCP2_BBR2_STATE_PROBE_BW_CRUISE: + case NGTCP2_BBR2_STATE_PROBE_BW_REFILL: + case NGTCP2_BBR2_STATE_PROBE_BW_UP: + return 1; + default: + return 0; + } +} + +static void bbr_update_ack_aggregation(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + ngtcp2_duration interval = ts - bbr->extra_acked_interval_start; + uint64_t expected_delivered = bbr->bw * interval / NGTCP2_SECONDS; + uint64_t extra; + + if (bbr->extra_acked_delivered <= expected_delivered) { + bbr->extra_acked_delivered = 0; + bbr->extra_acked_interval_start = ts; + expected_delivered = 0; + } + + bbr->extra_acked_delivered += ack->bytes_delivered; + extra = bbr->extra_acked_delivered - expected_delivered; + extra = ngtcp2_min(extra, cstat->cwnd); + + ngtcp2_window_filter_update(&bbr->extra_acked_filter, extra, + bbr->round_count); + + bbr->extra_acked = ngtcp2_window_filter_get_best(&bbr->extra_acked_filter); +} + +static void bbr_enter_drain(ngtcp2_bbr2_cc *bbr) { + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, "bbr2 enter Drain"); + + bbr->state = NGTCP2_BBR2_STATE_DRAIN; + bbr->pacing_gain = 1. / NGTCP2_BBR_STARTUP_CWND_GAIN; + bbr->cwnd_gain = NGTCP2_BBR_STARTUP_CWND_GAIN; +} + +static void bbr_check_drain(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (bbr->state == NGTCP2_BBR2_STATE_DRAIN && + cstat->bytes_in_flight <= bbr_inflight(bbr, cstat, bbr->bw, 1.0)) { + bbr_enter_probe_bw(bbr, ts); + } +} + +static void bbr_enter_probe_bw(ngtcp2_bbr2_cc *bbr, ngtcp2_tstamp ts) { + bbr_start_probe_bw_down(bbr, ts); +} + +static void bbr_start_probe_bw_down(ngtcp2_bbr2_cc *bbr, ngtcp2_tstamp ts) { + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr2 start ProbeBW_DOWN"); + + bbr_reset_congestion_signals(bbr); + + bbr->probe_up_cnt = UINT64_MAX; + + bbr_pick_probe_wait(bbr); + + bbr->cycle_stamp = ts; + bbr->ack_phase = NGTCP2_BBR2_ACK_PHASE_ACKS_PROBE_STOPPING; + + bbr_start_round(bbr); + + bbr->state = NGTCP2_BBR2_STATE_PROBE_BW_DOWN; + bbr->pacing_gain = 0.9; + bbr->cwnd_gain = 2; +} + +static void bbr_start_probe_bw_cruise(ngtcp2_bbr2_cc *bbr) { + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr2 start ProbeBW_CRUISE"); + + bbr->state = NGTCP2_BBR2_STATE_PROBE_BW_CRUISE; + bbr->pacing_gain = 1.0; + bbr->cwnd_gain = 2; +} + +static void bbr_start_probe_bw_refill(ngtcp2_bbr2_cc *bbr) { + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr2 start ProbeBW_REFILL"); + + bbr_reset_lower_bounds(bbr); + + bbr->bw_probe_up_rounds = 0; + bbr->bw_probe_up_acks = 0; + bbr->ack_phase = NGTCP2_BBR2_ACK_PHASE_ACKS_REFILLING; + + bbr_start_round(bbr); + + bbr->state = NGTCP2_BBR2_STATE_PROBE_BW_REFILL; + bbr->pacing_gain = 1.0; + bbr->cwnd_gain = 2; +} + +static void bbr_start_probe_bw_up(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, "bbr2 start ProbeBW_UP"); + + bbr->ack_phase = NGTCP2_BBR2_ACK_PHASE_ACKS_PROBE_STARTING; + + bbr_start_round(bbr); + + bbr->cycle_stamp = ts; + bbr->state = NGTCP2_BBR2_STATE_PROBE_BW_UP; + bbr->pacing_gain = 1.25; + bbr->cwnd_gain = 2; + + bbr_raise_inflight_hi_slope(bbr, cstat); +} + +static void bbr_update_probe_bw_cycle_phase(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + if (!bbr->filled_pipe) { + return; + } + + bbr_adapt_upper_bounds(bbr, cstat, ack, ts); + + if (!bbr_is_in_probe_bw_state(bbr)) { + return; + } + + switch (bbr->state) { + case NGTCP2_BBR2_STATE_PROBE_BW_DOWN: + if (bbr_check_time_to_probe_bw(bbr, cstat, ts)) { + return; + } + + if (bbr_check_time_to_cruise(bbr, cstat, ts)) { + bbr_start_probe_bw_cruise(bbr); + } + + break; + case NGTCP2_BBR2_STATE_PROBE_BW_CRUISE: + if (bbr_check_time_to_probe_bw(bbr, cstat, ts)) { + return; + } + + break; + case NGTCP2_BBR2_STATE_PROBE_BW_REFILL: + if (bbr->round_start) { + bbr->bw_probe_samples = 1; + bbr_start_probe_bw_up(bbr, cstat, ts); + } + + break; + case NGTCP2_BBR2_STATE_PROBE_BW_UP: + if (bbr_has_elapsed_in_phase(bbr, bbr->min_rtt, ts) && + cstat->bytes_in_flight > bbr_inflight(bbr, cstat, bbr->max_bw, 1.25)) { + bbr_start_probe_bw_down(bbr, ts); + } + + break; + default: + break; + } +} + +static int bbr_check_time_to_cruise(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { + (void)ts; + + if (cstat->bytes_in_flight > bbr_inflight_with_headroom(bbr, cstat)) { + return 0; + } + + if (cstat->bytes_in_flight <= bbr_inflight(bbr, cstat, bbr->max_bw, 1.0)) { + return 1; + } + + return 0; +} + +static int bbr_has_elapsed_in_phase(ngtcp2_bbr2_cc *bbr, + ngtcp2_duration interval, + ngtcp2_tstamp ts) { + return ts > bbr->cycle_stamp + interval; +} + +static uint64_t bbr_inflight_with_headroom(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t headroom; + uint64_t mpcwnd; + if (bbr->inflight_hi == UINT64_MAX) { + return UINT64_MAX; + } + + headroom = ngtcp2_max(cstat->max_tx_udp_payload_size, + bbr->inflight_hi * NGTCP2_BBR_HEADROOM_NUMER / + NGTCP2_BBR_HEADROOM_DENOM); + mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); + + if (bbr->inflight_hi > headroom) { + return ngtcp2_max(bbr->inflight_hi - headroom, mpcwnd); + } + + return mpcwnd; +} + +static void bbr_raise_inflight_hi_slope(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t growth_this_round = cstat->max_tx_udp_payload_size + << bbr->bw_probe_up_rounds; + + bbr->bw_probe_up_rounds = ngtcp2_min(bbr->bw_probe_up_rounds + 1, 30); + bbr->probe_up_cnt = ngtcp2_max(cstat->cwnd / growth_this_round, 1) * + cstat->max_tx_udp_payload_size; +} + +static void bbr_probe_inflight_hi_upward(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + uint64_t delta; + + if (!bbr->rst->is_cwnd_limited || cstat->cwnd < bbr->inflight_hi) { + return; + } + + bbr->bw_probe_up_acks += ack->bytes_delivered; + + if (bbr->bw_probe_up_acks >= bbr->probe_up_cnt) { + delta = bbr->bw_probe_up_acks / bbr->probe_up_cnt; + bbr->bw_probe_up_acks -= delta * bbr->probe_up_cnt; + bbr->inflight_hi += delta * cstat->max_tx_udp_payload_size; + } + + if (bbr->round_start) { + bbr_raise_inflight_hi_slope(bbr, cstat); + } +} + +static void bbr_adapt_upper_bounds(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + if (bbr->ack_phase == NGTCP2_BBR2_ACK_PHASE_ACKS_PROBE_STARTING && + bbr->round_start) { + bbr->ack_phase = NGTCP2_BBR2_ACK_PHASE_ACKS_PROBE_FEEDBACK; + } + + if (bbr->ack_phase == NGTCP2_BBR2_ACK_PHASE_ACKS_PROBE_STOPPING && + bbr->round_start) { + if (bbr_is_in_probe_bw_state(bbr) && !bbr->rst->rs.is_app_limited) { + bbr_advance_max_bw_filter(bbr); + } + } + + if (!bbr_check_inflight_too_high(bbr, cstat, ts)) { + /* bbr->bw_hi never be updated */ + if (bbr->inflight_hi == UINT64_MAX /* || bbr->bw_hi == UINT64_MAX */) { + return; + } + + if (bbr->rst->rs.tx_in_flight > bbr->inflight_hi) { + bbr->inflight_hi = bbr->rst->rs.tx_in_flight; + } + + if (cstat->delivery_rate_sec > bbr->bw_hi) { + bbr->bw_hi = cstat->delivery_rate_sec; + } + + if (bbr->state == NGTCP2_BBR2_STATE_PROBE_BW_UP) { + bbr_probe_inflight_hi_upward(bbr, cstat, ack); + } + } +} + +static int bbr_check_time_to_probe_bw(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (bbr_has_elapsed_in_phase(bbr, bbr->bw_probe_wait, ts) || + bbr_is_reno_coexistence_probe_time(bbr, cstat)) { + bbr_start_probe_bw_refill(bbr); + + return 1; + } + + return 0; +} + +static void bbr_pick_probe_wait(ngtcp2_bbr2_cc *bbr) { + uint8_t rand; + + bbr->rand(&rand, 1, &bbr->rand_ctx); + + bbr->rounds_since_bw_probe = (uint64_t)(rand * 2 / 256); + + bbr->rand(&rand, 1, &bbr->rand_ctx); + + bbr->bw_probe_wait = 2 * NGTCP2_SECONDS + + (ngtcp2_tstamp)((double)rand / 255. * NGTCP2_SECONDS); +} + +static int bbr_is_reno_coexistence_probe_time(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t reno_rounds = + bbr_target_inflight(bbr, cstat) / cstat->max_tx_udp_payload_size; + + return bbr->rounds_since_bw_probe >= ngtcp2_min(reno_rounds, 63); +} + +static uint64_t bbr_target_inflight(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t bdp = bbr_inflight(bbr, cstat, bbr->bw, 1.0); + + return ngtcp2_min(bdp, cstat->cwnd); +} + +static int bbr_check_inflight_too_high(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (is_inflight_too_high(&bbr->rst->rs)) { + if (bbr->bw_probe_samples) { + bbr_handle_inflight_too_high(bbr, cstat, &bbr->rst->rs, ts); + } + + return 1; + } + + return 0; +} + +static int is_inflight_too_high(const ngtcp2_rs *rs) { + return rs->lost * NGTCP2_BBR_LOSS_THRESH_DENOM > + rs->tx_in_flight * NGTCP2_BBR_LOSS_THRESH_NUMER; +} + +static void bbr_handle_inflight_too_high(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_rs *rs, + ngtcp2_tstamp ts) { + bbr->bw_probe_samples = 0; + + if (!rs->is_app_limited) { + bbr->prior_inflight_hi = bbr->inflight_hi; + + bbr->inflight_hi = ngtcp2_max( + rs->tx_in_flight, bbr_target_inflight(bbr, cstat) * + NGTCP2_BBR_BETA_NUMER / NGTCP2_BBR_BETA_DENOM); + } + + if (bbr->state == NGTCP2_BBR2_STATE_PROBE_BW_UP) { + bbr_start_probe_bw_down(bbr, ts); + } +} + +static void bbr_handle_lost_packet(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { + ngtcp2_rs rs = {0}; + + if (!bbr->bw_probe_samples) { + return; + } + + rs.tx_in_flight = pkt->tx_in_flight; + /* bbr->rst->lost is not incremented for pkt yet */ + rs.lost = bbr->rst->lost + pkt->pktlen - pkt->lost; + rs.is_app_limited = pkt->is_app_limited; + + if (is_inflight_too_high(&rs)) { + rs.tx_in_flight = bbr_inflight_hi_from_lost_packet(bbr, &rs, pkt); + + bbr_handle_inflight_too_high(bbr, cstat, &rs, ts); + } +} + +static uint64_t bbr_inflight_hi_from_lost_packet(ngtcp2_bbr2_cc *bbr, + const ngtcp2_rs *rs, + const ngtcp2_cc_pkt *pkt) { + uint64_t inflight_prev, lost_prev, lost_prefix; + (void)bbr; + + assert(rs->tx_in_flight >= pkt->pktlen); + + inflight_prev = rs->tx_in_flight - pkt->pktlen; + + assert(rs->lost >= pkt->pktlen); + + lost_prev = rs->lost - pkt->pktlen; + + if (inflight_prev * NGTCP2_BBR_LOSS_THRESH_NUMER < + lost_prev * NGTCP2_BBR_LOSS_THRESH_DENOM) { + return inflight_prev; + } + + lost_prefix = (inflight_prev * NGTCP2_BBR_LOSS_THRESH_NUMER - + lost_prev * NGTCP2_BBR_LOSS_THRESH_DENOM) / + (NGTCP2_BBR_LOSS_THRESH_DENOM - NGTCP2_BBR_LOSS_THRESH_NUMER); + + return inflight_prev + lost_prefix; +} + +static void bbr_update_min_rtt(ngtcp2_bbr2_cc *bbr, const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + int min_rtt_expired; + + bbr->probe_rtt_expired = + ts > bbr->probe_rtt_min_stamp + NGTCP2_BBR_PROBE_RTT_INTERVAL; + + if (ack->rtt != UINT64_MAX && + (ack->rtt < bbr->probe_rtt_min_delay || bbr->probe_rtt_expired)) { + bbr->probe_rtt_min_delay = ack->rtt; + bbr->probe_rtt_min_stamp = ts; + } + + min_rtt_expired = ts > bbr->min_rtt_stamp + NGTCP2_BBR_MIN_RTT_FILTERLEN; + + if (bbr->probe_rtt_min_delay < bbr->min_rtt || min_rtt_expired) { + bbr->min_rtt = bbr->probe_rtt_min_delay; + bbr->min_rtt_stamp = bbr->probe_rtt_min_stamp; + + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr2 update min_rtt=%" PRIu64, bbr->min_rtt); + } +} + +static void bbr_check_probe_rtt(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (bbr->state != NGTCP2_BBR2_STATE_PROBE_RTT && bbr->probe_rtt_expired && + !bbr->idle_restart) { + bbr_enter_probe_rtt(bbr); + bbr_save_cwnd(bbr, cstat); + + bbr->probe_rtt_done_stamp = UINT64_MAX; + bbr->ack_phase = NGTCP2_BBR2_ACK_PHASE_ACKS_PROBE_STOPPING; + + bbr_start_round(bbr); + } + + if (bbr->state == NGTCP2_BBR2_STATE_PROBE_RTT) { + bbr_handle_probe_rtt(bbr, cstat, ts); + } + + if (bbr->rst->rs.delivered) { + bbr->idle_restart = 0; + } +} + +static void bbr_enter_probe_rtt(ngtcp2_bbr2_cc *bbr) { + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, "bbr2 enter ProbeRTT"); + + bbr->state = NGTCP2_BBR2_STATE_PROBE_RTT; + bbr->pacing_gain = 1; + bbr->cwnd_gain = NGTCP2_BBR_PROBE_RTT_CWND_GAIN; +} + +static void bbr_handle_probe_rtt(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + bbr_mark_connection_app_limited(bbr, cstat); + + if (bbr->probe_rtt_done_stamp == UINT64_MAX && + cstat->bytes_in_flight <= bbr_probe_rtt_cwnd(bbr, cstat)) { + bbr->probe_rtt_done_stamp = ts + NGTCP2_BBR_PROBE_RTT_DURATION; + bbr->probe_rtt_round_done = 0; + + bbr_start_round(bbr); + + return; + } + + if (bbr->probe_rtt_done_stamp != UINT64_MAX) { + if (bbr->round_start) { + bbr->probe_rtt_round_done = 1; + } + + if (bbr->probe_rtt_round_done) { + bbr_check_probe_rtt_done(bbr, cstat, ts); + } + } +} + +static void bbr_check_probe_rtt_done(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (bbr->probe_rtt_done_stamp != UINT64_MAX && + ts > bbr->probe_rtt_done_stamp) { + bbr->probe_rtt_min_stamp = ts; + bbr_restore_cwnd(bbr, cstat); + bbr_exit_probe_rtt(bbr, ts); + } +} + +static void bbr_mark_connection_app_limited(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t app_limited = bbr->rst->delivered + cstat->bytes_in_flight; + + if (app_limited) { + bbr->rst->app_limited = app_limited; + } else { + bbr->rst->app_limited = cstat->max_tx_udp_payload_size; + } +} + +static void bbr_exit_probe_rtt(ngtcp2_bbr2_cc *bbr, ngtcp2_tstamp ts) { + bbr_reset_lower_bounds(bbr); + + if (bbr->filled_pipe) { + bbr_start_probe_bw_down(bbr, ts); + bbr_start_probe_bw_cruise(bbr); + } else { + bbr_enter_startup(bbr); + } +} + +static void bbr_handle_restart_from_idle(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + if (cstat->bytes_in_flight == 0 && bbr->rst->app_limited) { + ngtcp2_log_info(bbr->ccb.log, NGTCP2_LOG_EVENT_RCV, + "bbr2 restart from idle"); + + bbr->idle_restart = 1; + bbr->extra_acked_interval_start = ts; + + if (bbr_is_in_probe_bw_state(bbr)) { + bbr_set_pacing_rate_with_gain(bbr, cstat, 1); + } else if (bbr->state == NGTCP2_BBR2_STATE_PROBE_RTT) { + bbr_check_probe_rtt_done(bbr, cstat, ts); + } + } +} + +static uint64_t bbr_bdp_multiple(ngtcp2_bbr2_cc *bbr, uint64_t bw, + double gain) { + uint64_t bdp; + + if (bbr->min_rtt == UINT64_MAX) { + return bbr->initial_cwnd; + } + + bdp = bw * bbr->min_rtt / NGTCP2_SECONDS; + + return (uint64_t)(gain * (double)bdp); +} + +static uint64_t min_pipe_cwnd(size_t max_udp_payload_size) { + return max_udp_payload_size * 4; +} + +static uint64_t bbr_quantization_budget(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + uint64_t inflight) { + bbr_update_offload_budget(bbr, cstat); + + inflight = ngtcp2_max(inflight, bbr->offload_budget); + inflight = + ngtcp2_max(inflight, min_pipe_cwnd(cstat->max_tx_udp_payload_size)); + + if (bbr->state == NGTCP2_BBR2_STATE_PROBE_BW_UP) { + inflight += 2 * cstat->max_tx_udp_payload_size; + } + + return inflight; +} + +static uint64_t bbr_inflight(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + uint64_t bw, double gain) { + uint64_t inflight = bbr_bdp_multiple(bbr, bw, gain); + + return bbr_quantization_budget(bbr, cstat, inflight); +} + +static void bbr_update_max_inflight(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t inflight; + + /* Not documented */ + /* bbr_update_aggregation_budget(bbr); */ + + inflight = bbr_bdp_multiple(bbr, bbr->bw, bbr->cwnd_gain) + bbr->extra_acked; + bbr->max_inflight = bbr_quantization_budget(bbr, cstat, inflight); +} + +static void bbr_update_offload_budget(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + bbr->offload_budget = 3 * cstat->send_quantum; +} + +static void bbr_advance_max_bw_filter(ngtcp2_bbr2_cc *bbr) { + ++bbr->cycle_count; +} + +static void bbr_modulate_cwnd_for_recovery(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + if (ack->bytes_lost > 0) { + if (cstat->cwnd > ack->bytes_lost) { + cstat->cwnd -= ack->bytes_lost; + cstat->cwnd = ngtcp2_max(cstat->cwnd, 2 * cstat->max_tx_udp_payload_size); + } else { + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; + } + } + + if (bbr->packet_conservation) { + cstat->cwnd = + ngtcp2_max(cstat->cwnd, cstat->bytes_in_flight + ack->bytes_delivered); + } +} + +static void bbr_save_cwnd(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat) { + if (!bbr->in_loss_recovery && bbr->state != NGTCP2_BBR2_STATE_PROBE_RTT) { + bbr->prior_cwnd = cstat->cwnd; + return; + } + + bbr->prior_cwnd = ngtcp2_max(bbr->prior_cwnd, cstat->cwnd); +} + +static void bbr_restore_cwnd(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat) { + cstat->cwnd = ngtcp2_max(cstat->cwnd, bbr->prior_cwnd); +} + +static uint64_t bbr_probe_rtt_cwnd(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t probe_rtt_cwnd = + bbr_bdp_multiple(bbr, bbr->bw, NGTCP2_BBR_PROBE_RTT_CWND_GAIN); + uint64_t mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); + + return ngtcp2_max(probe_rtt_cwnd, mpcwnd); +} + +static void bbr_bound_cwnd_for_probe_rtt(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t probe_rtt_cwnd; + + if (bbr->state == NGTCP2_BBR2_STATE_PROBE_RTT) { + probe_rtt_cwnd = bbr_probe_rtt_cwnd(bbr, cstat); + + cstat->cwnd = ngtcp2_min(cstat->cwnd, probe_rtt_cwnd); + } +} + +static void bbr_set_cwnd(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + uint64_t mpcwnd; + + bbr_update_max_inflight(bbr, cstat); + bbr_modulate_cwnd_for_recovery(bbr, cstat, ack); + + if (!bbr->packet_conservation) { + if (bbr->filled_pipe) { + cstat->cwnd += ack->bytes_delivered; + cstat->cwnd = ngtcp2_min(cstat->cwnd, bbr->max_inflight); + } else if (cstat->cwnd < bbr->max_inflight || + bbr->rst->delivered < bbr->initial_cwnd) { + cstat->cwnd += ack->bytes_delivered; + } + + mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); + cstat->cwnd = ngtcp2_max(cstat->cwnd, mpcwnd); + } + + bbr_bound_cwnd_for_probe_rtt(bbr, cstat); + bbr_bound_cwnd_for_model(bbr, cstat); +} + +static void bbr_bound_cwnd_for_model(ngtcp2_bbr2_cc *bbr, + ngtcp2_conn_stat *cstat) { + uint64_t cap = UINT64_MAX; + uint64_t mpcwnd = min_pipe_cwnd(cstat->max_tx_udp_payload_size); + + if (bbr_is_in_probe_bw_state(bbr) && + bbr->state != NGTCP2_BBR2_STATE_PROBE_BW_CRUISE) { + cap = bbr->inflight_hi; + } else if (bbr->state == NGTCP2_BBR2_STATE_PROBE_RTT || + bbr->state == NGTCP2_BBR2_STATE_PROBE_BW_CRUISE) { + cap = bbr_inflight_with_headroom(bbr, cstat); + } + + cap = ngtcp2_min(cap, bbr->inflight_lo); + cap = ngtcp2_max(cap, mpcwnd); + + cstat->cwnd = ngtcp2_min(cstat->cwnd, cap); +} + +static void bbr_set_send_quantum(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat) { + size_t send_quantum = + (size_t)(cstat->pacing_rate * (double)(bbr->min_rtt == UINT64_MAX + ? NGTCP2_MILLISECONDS + : bbr->min_rtt)); + (void)bbr; + + cstat->send_quantum = ngtcp2_min(send_quantum, 64 * 1024); + cstat->send_quantum = + ngtcp2_max(cstat->send_quantum, cstat->max_tx_udp_payload_size * 10); +} + +static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_time) { + return cstat->congestion_recovery_start_ts != UINT64_MAX && + sent_time <= cstat->congestion_recovery_start_ts; +} + +static void bbr_handle_recovery(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack) { + if (bbr->in_loss_recovery) { + if (ack->pkt_delivered >= bbr->congestion_recovery_next_round_delivered) { + bbr->packet_conservation = 0; + } + + if (!in_congestion_recovery(cstat, ack->largest_acked_sent_ts)) { + bbr->in_loss_recovery = 0; + bbr->packet_conservation = 0; + bbr_restore_cwnd(bbr, cstat); + } + + return; + } + + if (bbr->congestion_recovery_start_ts != UINT64_MAX) { + bbr->in_loss_recovery = 1; + bbr_save_cwnd(bbr, cstat); + cstat->cwnd = + cstat->bytes_in_flight + + ngtcp2_max(ack->bytes_delivered, cstat->max_tx_udp_payload_size); + + cstat->congestion_recovery_start_ts = bbr->congestion_recovery_start_ts; + bbr->congestion_recovery_start_ts = UINT64_MAX; + bbr->packet_conservation = 1; + bbr->congestion_recovery_next_round_delivered = bbr->rst->delivered; + bbr->prior_inflight_lo = bbr->inflight_lo; + bbr->prior_bw_lo = bbr->bw_lo; + } +} + +static void bbr2_cc_init(ngtcp2_bbr2_cc *bbr, ngtcp2_conn_stat *cstat, + ngtcp2_rst *rst, ngtcp2_tstamp initial_ts, + ngtcp2_rand rand, const ngtcp2_rand_ctx *rand_ctx, + ngtcp2_log *log) { + bbr->ccb.log = log; + bbr->rst = rst; + bbr->rand = rand; + bbr->rand_ctx = *rand_ctx; + bbr->initial_cwnd = cstat->cwnd; + + bbr_on_init(bbr, cstat, initial_ts); +} + +static void bbr2_cc_free(ngtcp2_bbr2_cc *bbr) { (void)bbr; } + +static void bbr2_cc_on_pkt_acked(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { + (void)ccx; + (void)cstat; + (void)pkt; + (void)ts; +} + +static void bbr2_cc_on_pkt_lost(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts) { + ngtcp2_bbr2_cc *bbr = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr2_cc, ccb); + + bbr_update_on_loss(bbr, cstat, pkt, ts); +} + +static void bbr2_cc_congestion_event(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, ngtcp2_tstamp ts) { + ngtcp2_bbr2_cc *bbr = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr2_cc, ccb); + + if (!bbr->filled_pipe || bbr->in_loss_recovery || + bbr->congestion_recovery_start_ts != UINT64_MAX || + in_congestion_recovery(cstat, sent_ts)) { + return; + } + + bbr->congestion_recovery_start_ts = ts; +} + +static void bbr2_cc_on_spurious_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_bbr2_cc *bbr = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr2_cc, ccb); + (void)ts; + + bbr->congestion_recovery_start_ts = UINT64_MAX; + cstat->congestion_recovery_start_ts = UINT64_MAX; + + if (bbr->in_loss_recovery) { + bbr->in_loss_recovery = 0; + bbr->packet_conservation = 0; + bbr_restore_cwnd(bbr, cstat); + bbr->full_bw_count = 0; + bbr->loss_in_round = 0; + bbr->inflight_lo = ngtcp2_max(bbr->inflight_lo, bbr->prior_inflight_lo); + bbr->inflight_hi = ngtcp2_max(bbr->inflight_hi, bbr->prior_inflight_hi); + bbr->bw_lo = ngtcp2_max(bbr->bw_lo, bbr->prior_bw_lo); + } +} + +static void bbr2_cc_on_persistent_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_bbr2_cc *bbr = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr2_cc, ccb); + (void)ts; + + cstat->congestion_recovery_start_ts = UINT64_MAX; + bbr->congestion_recovery_start_ts = UINT64_MAX; + bbr->in_loss_recovery = 0; + bbr->packet_conservation = 0; + + bbr_save_cwnd(bbr, cstat); + cstat->cwnd = cstat->bytes_in_flight + cstat->max_tx_udp_payload_size; + cstat->cwnd = + ngtcp2_max(cstat->cwnd, min_pipe_cwnd(cstat->max_tx_udp_payload_size)); +} + +static void bbr2_cc_on_ack_recv(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + ngtcp2_bbr2_cc *bbr = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr2_cc, ccb); + + bbr_update_on_ack(bbr, cstat, ack, ts); +} + +static void bbr2_cc_on_pkt_sent(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt) { + ngtcp2_bbr2_cc *bbr = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr2_cc, ccb); + + bbr_on_transmit(bbr, cstat, pkt->sent_ts); +} + +static void bbr2_cc_new_rtt_sample(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + (void)ccx; + (void)cstat; + (void)ts; +} + +static void bbr2_cc_reset(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_bbr2_cc *bbr = ngtcp2_struct_of(ccx->ccb, ngtcp2_bbr2_cc, ccb); + + bbr_on_init(bbr, cstat, ts); +} + +static void bbr2_cc_event(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_cc_event_type event, ngtcp2_tstamp ts) { + (void)ccx; + (void)cstat; + (void)event; + (void)ts; +} + +int ngtcp2_cc_bbr2_cc_init(ngtcp2_cc *cc, ngtcp2_log *log, + ngtcp2_conn_stat *cstat, ngtcp2_rst *rst, + ngtcp2_tstamp initial_ts, ngtcp2_rand rand, + const ngtcp2_rand_ctx *rand_ctx, + const ngtcp2_mem *mem) { + ngtcp2_bbr2_cc *bbr; + + bbr = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_bbr2_cc)); + if (bbr == NULL) { + return NGTCP2_ERR_NOMEM; + } + + bbr2_cc_init(bbr, cstat, rst, initial_ts, rand, rand_ctx, log); + + cc->ccb = &bbr->ccb; + cc->on_pkt_acked = bbr2_cc_on_pkt_acked; + cc->on_pkt_lost = bbr2_cc_on_pkt_lost; + cc->congestion_event = bbr2_cc_congestion_event; + cc->on_spurious_congestion = bbr2_cc_on_spurious_congestion; + cc->on_persistent_congestion = bbr2_cc_on_persistent_congestion; + cc->on_ack_recv = bbr2_cc_on_ack_recv; + cc->on_pkt_sent = bbr2_cc_on_pkt_sent; + cc->new_rtt_sample = bbr2_cc_new_rtt_sample; + cc->reset = bbr2_cc_reset; + cc->event = bbr2_cc_event; + + return 0; +} + +void ngtcp2_cc_bbr2_cc_free(ngtcp2_cc *cc, const ngtcp2_mem *mem) { + ngtcp2_bbr2_cc *bbr = ngtcp2_struct_of(cc->ccb, ngtcp2_bbr2_cc, ccb); + + bbr2_cc_free(bbr); + ngtcp2_mem_free(mem, bbr); +} diff --git a/lib/ngtcp2_bbr2.h b/lib/ngtcp2_bbr2.h new file mode 100644 index 0000000..50dc05a --- /dev/null +++ b/lib/ngtcp2_bbr2.h @@ -0,0 +1,149 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_BBR2_H +#define NGTCP2_BBR2_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_cc.h" +#include "ngtcp2_window_filter.h" + +typedef struct ngtcp2_rst ngtcp2_rst; + +typedef enum ngtcp2_bbr2_state { + NGTCP2_BBR2_STATE_STARTUP, + NGTCP2_BBR2_STATE_DRAIN, + NGTCP2_BBR2_STATE_PROBE_BW_DOWN, + NGTCP2_BBR2_STATE_PROBE_BW_CRUISE, + NGTCP2_BBR2_STATE_PROBE_BW_REFILL, + NGTCP2_BBR2_STATE_PROBE_BW_UP, + NGTCP2_BBR2_STATE_PROBE_RTT, +} ngtcp2_bbr2_state; + +typedef enum ngtcp2_bbr2_ack_phase { + NGTCP2_BBR2_ACK_PHASE_ACKS_PROBE_STARTING, + NGTCP2_BBR2_ACK_PHASE_ACKS_PROBE_STOPPING, + NGTCP2_BBR2_ACK_PHASE_ACKS_PROBE_FEEDBACK, + NGTCP2_BBR2_ACK_PHASE_ACKS_REFILLING, +} ngtcp2_bbr2_ack_phase; + +/* + * ngtcp2_bbr2_cc is BBR v2 congestion controller, described in + * https://datatracker.ietf.org/doc/html/draft-cardwell-iccrg-bbr-congestion-control-01 + */ +typedef struct ngtcp2_bbr2_cc { + ngtcp2_cc_base ccb; + + uint64_t initial_cwnd; + ngtcp2_rst *rst; + ngtcp2_rand rand; + ngtcp2_rand_ctx rand_ctx; + + /* max_bw_filter for tracking the maximum recent delivery rate + samples for estimating max_bw. */ + ngtcp2_window_filter max_bw_filter; + + ngtcp2_window_filter extra_acked_filter; + + ngtcp2_duration min_rtt; + ngtcp2_tstamp min_rtt_stamp; + ngtcp2_tstamp probe_rtt_done_stamp; + int probe_rtt_round_done; + uint64_t prior_cwnd; + int idle_restart; + ngtcp2_tstamp extra_acked_interval_start; + uint64_t extra_acked_delivered; + + /* Congestion signals */ + int loss_in_round; + uint64_t bw_latest; + uint64_t inflight_latest; + + /* Lower bounds */ + uint64_t bw_lo; + uint64_t inflight_lo; + + /* Round counting */ + uint64_t next_round_delivered; + int round_start; + uint64_t round_count; + + /* Full pipe */ + int filled_pipe; + uint64_t full_bw; + size_t full_bw_count; + + /* Pacing rate */ + double pacing_gain; + + ngtcp2_bbr2_state state; + double cwnd_gain; + + int loss_round_start; + uint64_t loss_round_delivered; + uint64_t rounds_since_bw_probe; + uint64_t max_bw; + uint64_t bw; + uint64_t cycle_count; + uint64_t extra_acked; + uint64_t bytes_lost_in_round; + size_t loss_events_in_round; + uint64_t offload_budget; + uint64_t probe_up_cnt; + ngtcp2_tstamp cycle_stamp; + ngtcp2_bbr2_ack_phase ack_phase; + ngtcp2_duration bw_probe_wait; + int bw_probe_samples; + size_t bw_probe_up_rounds; + uint64_t bw_probe_up_acks; + uint64_t inflight_hi; + uint64_t bw_hi; + int probe_rtt_expired; + ngtcp2_duration probe_rtt_min_delay; + ngtcp2_tstamp probe_rtt_min_stamp; + int in_loss_recovery; + int packet_conservation; + uint64_t max_inflight; + ngtcp2_tstamp congestion_recovery_start_ts; + uint64_t congestion_recovery_next_round_delivered; + + uint64_t prior_inflight_lo; + uint64_t prior_inflight_hi; + uint64_t prior_bw_lo; +} ngtcp2_bbr2_cc; + +int ngtcp2_cc_bbr2_cc_init(ngtcp2_cc *cc, ngtcp2_log *log, + ngtcp2_conn_stat *cstat, ngtcp2_rst *rst, + ngtcp2_tstamp initial_ts, ngtcp2_rand rand, + const ngtcp2_rand_ctx *rand_ctx, + const ngtcp2_mem *mem); + +void ngtcp2_cc_bbr2_cc_free(ngtcp2_cc *cc, const ngtcp2_mem *mem); + +#endif /* NGTCP2_BBR2_H */ diff --git a/lib/ngtcp2_buf.c b/lib/ngtcp2_buf.c new file mode 100644 index 0000000..75326d6 --- /dev/null +++ b/lib/ngtcp2_buf.c @@ -0,0 +1,56 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_buf.h" +#include "ngtcp2_mem.h" + +void ngtcp2_buf_init(ngtcp2_buf *buf, uint8_t *begin, size_t len) { + buf->begin = buf->pos = buf->last = begin; + buf->end = begin + len; +} + +void ngtcp2_buf_reset(ngtcp2_buf *buf) { buf->pos = buf->last = buf->begin; } + +size_t ngtcp2_buf_cap(const ngtcp2_buf *buf) { + return (size_t)(buf->end - buf->begin); +} + +int ngtcp2_buf_chain_new(ngtcp2_buf_chain **pbufchain, size_t len, + const ngtcp2_mem *mem) { + *pbufchain = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_buf_chain) + len); + if (*pbufchain == NULL) { + return NGTCP2_ERR_NOMEM; + } + + (*pbufchain)->next = NULL; + + ngtcp2_buf_init(&(*pbufchain)->buf, + (uint8_t *)(*pbufchain) + sizeof(ngtcp2_buf_chain), len); + + return 0; +} + +void ngtcp2_buf_chain_del(ngtcp2_buf_chain *bufchain, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, bufchain); +} diff --git a/lib/ngtcp2_buf.h b/lib/ngtcp2_buf.h new file mode 100644 index 0000000..107d413 --- /dev/null +++ b/lib/ngtcp2_buf.h @@ -0,0 +1,108 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_BUF_H +#define NGTCP2_BUF_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +typedef struct ngtcp2_buf { + /* begin points to the beginning of the buffer. */ + uint8_t *begin; + /* end points to the one beyond of the last byte of the buffer */ + uint8_t *end; + /* pos pointers to the start of data. Typically, this points to the + point that next data should be read. Initially, it points to + |begin|. */ + uint8_t *pos; + /* last points to the one beyond of the last data of the buffer. + Typically, new data is written at this point. Initially, it + points to |begin|. */ + uint8_t *last; +} ngtcp2_buf; + +/* + * ngtcp2_buf_init initializes |buf| with the given buffer. + */ +void ngtcp2_buf_init(ngtcp2_buf *buf, uint8_t *begin, size_t len); + +/* + * ngtcp2_buf_reset resets pos and last fields to match begin field to + * make ngtcp2_buf_len(buf) return 0. + */ +void ngtcp2_buf_reset(ngtcp2_buf *buf); + +/* + * ngtcp2_buf_left returns the number of additional bytes which can be + * written to the underlying buffer. In other words, it returns + * buf->end - buf->last. + */ +#define ngtcp2_buf_left(BUF) (size_t)((BUF)->end - (BUF)->last) + +/* + * ngtcp2_buf_len returns the number of bytes left to read. In other + * words, it returns buf->last - buf->pos. + */ +#define ngtcp2_buf_len(BUF) (size_t)((BUF)->last - (BUF)->pos) + +/* + * ngtcp2_buf_cap returns the capacity of the buffer. In other words, + * it returns buf->end - buf->begin. + */ +size_t ngtcp2_buf_cap(const ngtcp2_buf *buf); + +/* + * ngtcp2_buf_chain is a linked list of ngtcp2_buf. + */ +typedef struct ngtcp2_buf_chain ngtcp2_buf_chain; + +struct ngtcp2_buf_chain { + ngtcp2_buf_chain *next; + ngtcp2_buf buf; +}; + +/* + * ngtcp2_buf_chain_new creates new ngtcp2_buf_chain and initializes + * the internal buffer with |len| bytes space. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_buf_chain_new(ngtcp2_buf_chain **pbufchain, size_t len, + const ngtcp2_mem *mem); + +/* + * ngtcp2_buf_chain_del deletes the resource allocated by |bufchain|. + * It also deletes the memory pointed by |bufchain|. + */ +void ngtcp2_buf_chain_del(ngtcp2_buf_chain *bufchain, const ngtcp2_mem *mem); + +#endif /* NGTCP2_BUF_H */ diff --git a/lib/ngtcp2_cc.c b/lib/ngtcp2_cc.c new file mode 100644 index 0000000..0536639 --- /dev/null +++ b/lib/ngtcp2_cc.c @@ -0,0 +1,615 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_cc.h" + +#include <assert.h> + +#if defined(_MSC_VER) +# include <intrin.h> +#endif + +#include "ngtcp2_log.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_rcvry.h" + +uint64_t ngtcp2_cc_compute_initcwnd(size_t max_udp_payload_size) { + uint64_t n = 2 * max_udp_payload_size; + n = ngtcp2_max(n, 14720); + return ngtcp2_min(10 * max_udp_payload_size, n); +} + +ngtcp2_cc_pkt *ngtcp2_cc_pkt_init(ngtcp2_cc_pkt *pkt, int64_t pkt_num, + size_t pktlen, ngtcp2_pktns_id pktns_id, + ngtcp2_tstamp sent_ts, uint64_t lost, + uint64_t tx_in_flight, int is_app_limited) { + pkt->pkt_num = pkt_num; + pkt->pktlen = pktlen; + pkt->pktns_id = pktns_id; + pkt->sent_ts = sent_ts; + pkt->lost = lost; + pkt->tx_in_flight = tx_in_flight; + pkt->is_app_limited = is_app_limited; + + return pkt; +} + +static void reno_cc_reset(ngtcp2_reno_cc *cc) { + cc->max_delivery_rate_sec = 0; + cc->target_cwnd = 0; + cc->pending_add = 0; +} + +void ngtcp2_reno_cc_init(ngtcp2_reno_cc *cc, ngtcp2_log *log) { + cc->ccb.log = log; + reno_cc_reset(cc); +} + +void ngtcp2_reno_cc_free(ngtcp2_reno_cc *cc) { (void)cc; } + +int ngtcp2_cc_reno_cc_init(ngtcp2_cc *cc, ngtcp2_log *log, + const ngtcp2_mem *mem) { + ngtcp2_reno_cc *reno_cc; + + reno_cc = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_reno_cc)); + if (reno_cc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_reno_cc_init(reno_cc, log); + + cc->ccb = &reno_cc->ccb; + cc->on_pkt_acked = ngtcp2_cc_reno_cc_on_pkt_acked; + cc->congestion_event = ngtcp2_cc_reno_cc_congestion_event; + cc->on_persistent_congestion = ngtcp2_cc_reno_cc_on_persistent_congestion; + cc->on_ack_recv = ngtcp2_cc_reno_cc_on_ack_recv; + cc->reset = ngtcp2_cc_reno_cc_reset; + + return 0; +} + +void ngtcp2_cc_reno_cc_free(ngtcp2_cc *cc, const ngtcp2_mem *mem) { + ngtcp2_reno_cc *reno_cc = ngtcp2_struct_of(cc->ccb, ngtcp2_reno_cc, ccb); + + ngtcp2_reno_cc_free(reno_cc); + ngtcp2_mem_free(mem, reno_cc); +} + +static int in_congestion_recovery(const ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_time) { + return cstat->congestion_recovery_start_ts != UINT64_MAX && + sent_time <= cstat->congestion_recovery_start_ts; +} + +void ngtcp2_cc_reno_cc_on_pkt_acked(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts) { + ngtcp2_reno_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_reno_cc, ccb); + uint64_t m; + (void)ts; + + if (in_congestion_recovery(cstat, pkt->sent_ts)) { + return; + } + + if (cc->target_cwnd && cc->target_cwnd < cstat->cwnd) { + return; + } + + if (cstat->cwnd < cstat->ssthresh) { + cstat->cwnd += pkt->pktlen; + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 " acked, slow start cwnd=%" PRIu64, + pkt->pkt_num, cstat->cwnd); + return; + } + + m = cstat->max_tx_udp_payload_size * pkt->pktlen + cc->pending_add; + cc->pending_add = m % cstat->cwnd; + + cstat->cwnd += m / cstat->cwnd; +} + +void ngtcp2_cc_reno_cc_congestion_event(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts) { + ngtcp2_reno_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_reno_cc, ccb); + uint64_t min_cwnd; + + if (in_congestion_recovery(cstat, sent_ts)) { + return; + } + + cstat->congestion_recovery_start_ts = ts; + cstat->cwnd >>= NGTCP2_LOSS_REDUCTION_FACTOR_BITS; + min_cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->cwnd = ngtcp2_max(cstat->cwnd, min_cwnd); + cstat->ssthresh = cstat->cwnd; + + cc->pending_add = 0; + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "reduce cwnd because of packet loss cwnd=%" PRIu64, + cstat->cwnd); +} + +void ngtcp2_cc_reno_cc_on_persistent_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + (void)ccx; + (void)ts; + + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->congestion_recovery_start_ts = UINT64_MAX; +} + +void ngtcp2_cc_reno_cc_on_ack_recv(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts) { + ngtcp2_reno_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_reno_cc, ccb); + uint64_t target_cwnd, initcwnd; + (void)ack; + (void)ts; + + /* TODO Use sliding window for min rtt measurement */ + /* TODO Use sliding window */ + cc->max_delivery_rate_sec = + ngtcp2_max(cc->max_delivery_rate_sec, cstat->delivery_rate_sec); + + if (cstat->min_rtt != UINT64_MAX && cc->max_delivery_rate_sec) { + target_cwnd = cc->max_delivery_rate_sec * cstat->min_rtt / NGTCP2_SECONDS; + initcwnd = ngtcp2_cc_compute_initcwnd(cstat->max_tx_udp_payload_size); + cc->target_cwnd = ngtcp2_max(initcwnd, target_cwnd) * 289 / 100; + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "target_cwnd=%" PRIu64 " max_delivery_rate_sec=%" PRIu64 + " min_rtt=%" PRIu64, + cc->target_cwnd, cc->max_delivery_rate_sec, cstat->min_rtt); + } +} + +void ngtcp2_cc_reno_cc_reset(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_reno_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_reno_cc, ccb); + (void)cstat; + (void)ts; + + reno_cc_reset(cc); +} + +static void cubic_cc_reset(ngtcp2_cubic_cc *cc) { + cc->max_delivery_rate_sec = 0; + cc->target_cwnd = 0; + cc->w_last_max = 0; + cc->w_tcp = 0; + cc->origin_point = 0; + cc->epoch_start = UINT64_MAX; + cc->k = 0; + + cc->prior.cwnd = 0; + cc->prior.ssthresh = 0; + cc->prior.w_last_max = 0; + cc->prior.w_tcp = 0; + cc->prior.origin_point = 0; + cc->prior.epoch_start = UINT64_MAX; + cc->prior.k = 0; + + cc->rtt_sample_count = 0; + cc->current_round_min_rtt = UINT64_MAX; + cc->last_round_min_rtt = UINT64_MAX; + cc->window_end = -1; +} + +void ngtcp2_cubic_cc_init(ngtcp2_cubic_cc *cc, ngtcp2_log *log) { + cc->ccb.log = log; + cubic_cc_reset(cc); +} + +void ngtcp2_cubic_cc_free(ngtcp2_cubic_cc *cc) { (void)cc; } + +int ngtcp2_cc_cubic_cc_init(ngtcp2_cc *cc, ngtcp2_log *log, + const ngtcp2_mem *mem) { + ngtcp2_cubic_cc *cubic_cc; + + cubic_cc = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_cubic_cc)); + if (cubic_cc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_cubic_cc_init(cubic_cc, log); + + cc->ccb = &cubic_cc->ccb; + cc->on_pkt_acked = ngtcp2_cc_cubic_cc_on_pkt_acked; + cc->congestion_event = ngtcp2_cc_cubic_cc_congestion_event; + cc->on_spurious_congestion = ngtcp2_cc_cubic_cc_on_spurious_congestion; + cc->on_persistent_congestion = ngtcp2_cc_cubic_cc_on_persistent_congestion; + cc->on_ack_recv = ngtcp2_cc_cubic_cc_on_ack_recv; + cc->on_pkt_sent = ngtcp2_cc_cubic_cc_on_pkt_sent; + cc->new_rtt_sample = ngtcp2_cc_cubic_cc_new_rtt_sample; + cc->reset = ngtcp2_cc_cubic_cc_reset; + cc->event = ngtcp2_cc_cubic_cc_event; + + return 0; +} + +void ngtcp2_cc_cubic_cc_free(ngtcp2_cc *cc, const ngtcp2_mem *mem) { + ngtcp2_cubic_cc *cubic_cc = ngtcp2_struct_of(cc->ccb, ngtcp2_cubic_cc, ccb); + + ngtcp2_cubic_cc_free(cubic_cc); + ngtcp2_mem_free(mem, cubic_cc); +} + +static uint64_t ngtcp2_cbrt(uint64_t n) { + int d; + uint64_t a; + + if (n == 0) { + return 0; + } + +#if defined(_MSC_VER) +# if defined(_M_X64) + d = (int)__lzcnt64(n); +# elif defined(_M_ARM64) + { + unsigned long index; + d = sizeof(uint64_t) * CHAR_BIT; + if (_BitScanReverse64(&index, n)) { + d = d - 1 - index; + } + } +# else + if ((n >> 32) != 0) { + d = __lzcnt((unsigned int)(n >> 32)); + } else { + d = 32 + __lzcnt((unsigned int)n); + } +# endif +#else + d = __builtin_clzll(n); +#endif + a = 1ULL << ((64 - d) / 3 + 1); + + for (; a * a * a > n;) { + a = (2 * a + n / a / a) / 3; + } + return a; +} + +/* HyStart++ constants */ +#define NGTCP2_HS_MIN_SSTHRESH 16 +#define NGTCP2_HS_N_RTT_SAMPLE 8 +#define NGTCP2_HS_MIN_ETA (4 * NGTCP2_MILLISECONDS) +#define NGTCP2_HS_MAX_ETA (16 * NGTCP2_MILLISECONDS) + +void ngtcp2_cc_cubic_cc_on_pkt_acked(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts) { + ngtcp2_cubic_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_cubic_cc, ccb); + ngtcp2_duration t, eta; + uint64_t target, cwnd_thres; + uint64_t tx, kx, time_delta, delta; + uint64_t add, tcp_add; + uint64_t m; + + if (pkt->pktns_id == NGTCP2_PKTNS_ID_APPLICATION && cc->window_end != -1 && + cc->window_end <= pkt->pkt_num) { + cc->window_end = -1; + } + + if (in_congestion_recovery(cstat, pkt->sent_ts)) { + return; + } + + if (cc->target_cwnd && cc->target_cwnd < cstat->cwnd) { + return; + } + + if (cstat->cwnd < cstat->ssthresh) { + /* slow-start */ + cstat->cwnd += pkt->pktlen; + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 " acked, slow start cwnd=%" PRIu64, + pkt->pkt_num, cstat->cwnd); + + if (cc->last_round_min_rtt != UINT64_MAX && + cc->current_round_min_rtt != UINT64_MAX && + cstat->cwnd >= + NGTCP2_HS_MIN_SSTHRESH * cstat->max_tx_udp_payload_size && + cc->rtt_sample_count >= NGTCP2_HS_N_RTT_SAMPLE) { + eta = cc->last_round_min_rtt / 8; + + if (eta < NGTCP2_HS_MIN_ETA) { + eta = NGTCP2_HS_MIN_ETA; + } else if (eta > NGTCP2_HS_MAX_ETA) { + eta = NGTCP2_HS_MAX_ETA; + } + + if (cc->current_round_min_rtt >= cc->last_round_min_rtt + eta) { + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "HyStart++ exit slow start"); + + cc->w_last_max = cstat->cwnd; + cstat->ssthresh = cstat->cwnd; + } + } + + return; + } + + /* congestion avoidance */ + + if (cc->epoch_start == UINT64_MAX) { + cc->epoch_start = ts; + if (cstat->cwnd < cc->w_last_max) { + cc->k = ngtcp2_cbrt((cc->w_last_max - cstat->cwnd) * 10 / 4 / + cstat->max_tx_udp_payload_size); + cc->origin_point = cc->w_last_max; + } else { + cc->k = 0; + cc->origin_point = cstat->cwnd; + } + + cc->w_tcp = cstat->cwnd; + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "cubic-ca epoch_start=%" PRIu64 " k=%" PRIu64 + " origin_point=%" PRIu64, + cc->epoch_start, cc->k, cc->origin_point); + + cc->pending_add = 0; + cc->pending_w_add = 0; + } + + t = ts - cc->epoch_start; + + tx = (t << 10) / NGTCP2_SECONDS; + kx = (cc->k << 10); + + if (tx > kx) { + time_delta = tx - kx; + } else { + time_delta = kx - tx; + } + + delta = cstat->max_tx_udp_payload_size * + ((((time_delta * time_delta) >> 10) * time_delta) >> 10) * 4 / 10; + delta >>= 10; + + if (tx > kx) { + target = cc->origin_point + delta; + } else { + target = cc->origin_point - delta; + } + + cwnd_thres = + (target * (((t + cstat->smoothed_rtt) << 10) / NGTCP2_SECONDS)) >> 10; + if (cwnd_thres < cstat->cwnd) { + target = cstat->cwnd; + } else if (2 * cwnd_thres > 3 * cstat->cwnd) { + target = cstat->cwnd * 3 / 2; + } else { + target = cwnd_thres; + } + + if (target > cstat->cwnd) { + m = cc->pending_add + + cstat->max_tx_udp_payload_size * (target - cstat->cwnd); + add = m / cstat->cwnd; + cc->pending_add = m % cstat->cwnd; + } else { + m = cc->pending_add + cstat->max_tx_udp_payload_size; + add = m / (100 * cstat->cwnd); + cc->pending_add = m % (100 * cstat->cwnd); + } + + m = cc->pending_w_add + cstat->max_tx_udp_payload_size * pkt->pktlen; + + cc->w_tcp += m / cstat->cwnd; + cc->pending_w_add = m % cstat->cwnd; + + if (cc->w_tcp > cstat->cwnd) { + tcp_add = cstat->max_tx_udp_payload_size * (cc->w_tcp - cstat->cwnd) / + cstat->cwnd; + if (tcp_add > add) { + add = tcp_add; + } + } + + cstat->cwnd += add; + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 " acked, cubic-ca cwnd=%" PRIu64 " t=%" PRIu64 + " k=%" PRIi64 " time_delta=%" PRIu64 " delta=%" PRIu64 + " target=%" PRIu64 " w_tcp=%" PRIu64, + pkt->pkt_num, cstat->cwnd, t, cc->k, time_delta >> 4, delta, + target, cc->w_tcp); +} + +void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts) { + ngtcp2_cubic_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_cubic_cc, ccb); + uint64_t min_cwnd; + + if (in_congestion_recovery(cstat, sent_ts)) { + return; + } + + if (cc->prior.cwnd < cstat->cwnd) { + cc->prior.cwnd = cstat->cwnd; + cc->prior.ssthresh = cstat->ssthresh; + cc->prior.w_last_max = cc->w_last_max; + cc->prior.w_tcp = cc->w_tcp; + cc->prior.origin_point = cc->origin_point; + cc->prior.epoch_start = cc->epoch_start; + cc->prior.k = cc->k; + } + + cstat->congestion_recovery_start_ts = ts; + + cc->epoch_start = UINT64_MAX; + if (cstat->cwnd < cc->w_last_max) { + cc->w_last_max = cstat->cwnd * 17 / 10 / 2; + } else { + cc->w_last_max = cstat->cwnd; + } + + min_cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->ssthresh = cstat->cwnd * 7 / 10; + cstat->ssthresh = ngtcp2_max(cstat->ssthresh, min_cwnd); + cstat->cwnd = cstat->ssthresh; + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "reduce cwnd because of packet loss cwnd=%" PRIu64, + cstat->cwnd); +} + +void ngtcp2_cc_cubic_cc_on_spurious_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_cubic_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_cubic_cc, ccb); + (void)ts; + + if (cstat->cwnd >= cc->prior.cwnd) { + return; + } + + cstat->congestion_recovery_start_ts = UINT64_MAX; + + cstat->cwnd = cc->prior.cwnd; + cstat->ssthresh = cc->prior.ssthresh; + cc->w_last_max = cc->prior.w_last_max; + cc->w_tcp = cc->prior.w_tcp; + cc->origin_point = cc->prior.origin_point; + cc->epoch_start = cc->prior.epoch_start; + cc->k = cc->prior.k; + + cc->prior.cwnd = 0; + cc->prior.ssthresh = 0; + cc->prior.w_last_max = 0; + cc->prior.w_tcp = 0; + cc->prior.origin_point = 0; + cc->prior.epoch_start = UINT64_MAX; + cc->prior.k = 0; + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "spurious congestion is detected and congestion state is " + "restored cwnd=%" PRIu64, + cstat->cwnd); +} + +void ngtcp2_cc_cubic_cc_on_persistent_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + (void)ccx; + (void)ts; + + cstat->cwnd = 2 * cstat->max_tx_udp_payload_size; + cstat->congestion_recovery_start_ts = UINT64_MAX; +} + +void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts) { + ngtcp2_cubic_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_cubic_cc, ccb); + uint64_t target_cwnd, initcwnd; + (void)ack; + (void)ts; + + /* TODO Use sliding window for min rtt measurement */ + /* TODO Use sliding window */ + cc->max_delivery_rate_sec = + ngtcp2_max(cc->max_delivery_rate_sec, cstat->delivery_rate_sec); + + if (cstat->min_rtt != UINT64_MAX && cc->max_delivery_rate_sec) { + target_cwnd = cc->max_delivery_rate_sec * cstat->min_rtt / NGTCP2_SECONDS; + initcwnd = ngtcp2_cc_compute_initcwnd(cstat->max_tx_udp_payload_size); + cc->target_cwnd = ngtcp2_max(initcwnd, target_cwnd) * 289 / 100; + + ngtcp2_log_info(cc->ccb.log, NGTCP2_LOG_EVENT_RCV, + "target_cwnd=%" PRIu64 " max_delivery_rate_sec=%" PRIu64 + " min_rtt=%" PRIu64, + cc->target_cwnd, cc->max_delivery_rate_sec, cstat->min_rtt); + } +} + +void ngtcp2_cc_cubic_cc_on_pkt_sent(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt) { + ngtcp2_cubic_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_cubic_cc, ccb); + (void)cstat; + + if (pkt->pktns_id != NGTCP2_PKTNS_ID_APPLICATION || cc->window_end != -1) { + return; + } + + cc->window_end = pkt->pkt_num; + cc->last_round_min_rtt = cc->current_round_min_rtt; + cc->current_round_min_rtt = UINT64_MAX; + cc->rtt_sample_count = 0; +} + +void ngtcp2_cc_cubic_cc_new_rtt_sample(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_cubic_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_cubic_cc, ccb); + (void)ts; + + if (cc->window_end == -1) { + return; + } + + cc->current_round_min_rtt = + ngtcp2_min(cc->current_round_min_rtt, cstat->latest_rtt); + ++cc->rtt_sample_count; +} + +void ngtcp2_cc_cubic_cc_reset(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + ngtcp2_cubic_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_cubic_cc, ccb); + (void)cstat; + (void)ts; + + cubic_cc_reset(cc); +} + +void ngtcp2_cc_cubic_cc_event(ngtcp2_cc *ccx, ngtcp2_conn_stat *cstat, + ngtcp2_cc_event_type event, ngtcp2_tstamp ts) { + ngtcp2_cubic_cc *cc = ngtcp2_struct_of(ccx->ccb, ngtcp2_cubic_cc, ccb); + ngtcp2_tstamp last_ts; + + if (event != NGTCP2_CC_EVENT_TYPE_TX_START || cc->epoch_start == UINT64_MAX) { + return; + } + + last_ts = cstat->last_tx_pkt_ts[NGTCP2_PKTNS_ID_APPLICATION]; + if (last_ts == UINT64_MAX || last_ts <= cc->epoch_start) { + return; + } + + assert(ts >= last_ts); + + cc->epoch_start += ts - last_ts; +} diff --git a/lib/ngtcp2_cc.h b/lib/ngtcp2_cc.h new file mode 100644 index 0000000..6d9e0c2 --- /dev/null +++ b/lib/ngtcp2_cc.h @@ -0,0 +1,421 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CC_H +#define NGTCP2_CC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#define NGTCP2_LOSS_REDUCTION_FACTOR_BITS 1 +#define NGTCP2_PERSISTENT_CONGESTION_THRESHOLD 3 + +typedef struct ngtcp2_log ngtcp2_log; + +/** + * @struct + * + * :type:`ngtcp2_cc_base` is the base structure of custom congestion + * control algorithm. It must be the first field of custom congestion + * controller. + */ +typedef struct ngtcp2_cc_base { + /** + * :member:`log` is ngtcp2 library internal logger. + */ + ngtcp2_log *log; +} ngtcp2_cc_base; + +/** + * @struct + * + * :type:`ngtcp2_cc_pkt` is a convenient structure to include + * acked/lost/sent packet. + */ +typedef struct ngtcp2_cc_pkt { + /** + * :member:`pkt_num` is the packet number + */ + int64_t pkt_num; + /** + * :member:`pktlen` is the length of packet. + */ + size_t pktlen; + /** + * :member:`pktns_id` is the ID of packet number space which this + * packet belongs to. + */ + ngtcp2_pktns_id pktns_id; + /** + * :member:`sent_ts` is the timestamp when packet is sent. + */ + ngtcp2_tstamp sent_ts; + /** + * :member:`lost` is the number of bytes lost when this packet was + * sent. + */ + uint64_t lost; + /** + * :member:`tx_in_flight` is the bytes in flight when this packet + * was sent. + */ + uint64_t tx_in_flight; + /** + * :member:`is_app_limited` is nonzero if the connection is + * app-limited when this packet was sent. + */ + int is_app_limited; +} ngtcp2_cc_pkt; + +/** + * @struct + * + * :type:`ngtcp2_cc_ack` is a convenient structure which stores + * acknowledged and lost bytes. + */ +typedef struct ngtcp2_cc_ack { + /** + * :member:`prior_bytes_in_flight` is the in-flight bytes before + * processing this ACK. + */ + uint64_t prior_bytes_in_flight; + /** + * :member:`bytes_delivered` is the number of bytes acknowledged. + */ + uint64_t bytes_delivered; + /** + * :member:`bytes_lost` is the number of bytes declared lost. + */ + uint64_t bytes_lost; + /** + * :member:`pkt_delivered` is the cumulative acknowledged bytes when + * the last packet acknowledged by this ACK was sent. + */ + uint64_t pkt_delivered; + /** + * :member:`largest_acked_sent_ts` is the time when the largest + * acknowledged packet was sent. + */ + ngtcp2_tstamp largest_acked_sent_ts; + /** + * :member:`rtt` is the RTT sample. It is UINT64_MAX if no RTT + * sample is available. + */ + ngtcp2_duration rtt; +} ngtcp2_cc_ack; + +typedef struct ngtcp2_cc ngtcp2_cc; + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_pkt_acked` is a callback function which is + * called with an acknowledged packet. + */ +typedef void (*ngtcp2_cc_on_pkt_acked)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_pkt_lost` is a callback function which is + * called with a lost packet. + */ +typedef void (*ngtcp2_cc_on_pkt_lost)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts); +/** + * @functypedef + * + * :type:`ngtcp2_cc_congestion_event` is a callback function which is + * called when congestion event happens (e.g., when packet is lost). + */ +typedef void (*ngtcp2_cc_congestion_event)(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_spurious_congestion` is a callback function + * which is called when a spurious congestion is detected. + */ +typedef void (*ngtcp2_cc_on_spurious_congestion)(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_persistent_congestion` is a callback function + * which is called when persistent congestion is established. + */ +typedef void (*ngtcp2_cc_on_persistent_congestion)(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_ack_recv` is a callback function which is + * called when an acknowledgement is received. + */ +typedef void (*ngtcp2_cc_on_ack_recv)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_on_pkt_sent` is a callback function which is + * called when an ack-eliciting packet is sent. + */ +typedef void (*ngtcp2_cc_on_pkt_sent)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_new_rtt_sample` is a callback function which is + * called when new RTT sample is obtained. + */ +typedef void (*ngtcp2_cc_new_rtt_sample)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/** + * @functypedef + * + * :type:`ngtcp2_cc_reset` is a callback function which is called when + * congestion state must be reset. + */ +typedef void (*ngtcp2_cc_reset)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/** + * @enum + * + * :type:`ngtcp2_cc_event_type` defines congestion control events. + */ +typedef enum ngtcp2_cc_event_type { + /** + * :enum:`NGTCP2_CC_EVENT_TX_START` occurs when ack-eliciting packet + * is sent and no other ack-eliciting packet is present. + */ + NGTCP2_CC_EVENT_TYPE_TX_START +} ngtcp2_cc_event_type; + +/** + * @functypedef + * + * :type:`ngtcp2_cc_event` is a callback function which is called when + * a specific event happens. + */ +typedef void (*ngtcp2_cc_event)(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_cc_event_type event, ngtcp2_tstamp ts); + +/** + * @struct + * + * :type:`ngtcp2_cc` is congestion control algorithm interface to + * allow custom implementation. + */ +typedef struct ngtcp2_cc { + /** + * :member:`ccb` is a pointer to :type:`ngtcp2_cc_base` which + * usually contains a state. + */ + ngtcp2_cc_base *ccb; + /** + * :member:`on_pkt_acked` is a callback function which is called + * when a packet is acknowledged. + */ + ngtcp2_cc_on_pkt_acked on_pkt_acked; + /** + * :member:`on_pkt_lost` is a callback function which is called when + * a packet is lost. + */ + ngtcp2_cc_on_pkt_lost on_pkt_lost; + /** + * :member:`congestion_event` is a callback function which is called + * when congestion event happens (.e.g, packet is lost). + */ + ngtcp2_cc_congestion_event congestion_event; + /** + * :member:`on_spurious_congestion` is a callback function which is + * called when a spurious congestion is detected. + */ + ngtcp2_cc_on_spurious_congestion on_spurious_congestion; + /** + * :member:`on_persistent_congestion` is a callback function which + * is called when persistent congestion is established. + */ + ngtcp2_cc_on_persistent_congestion on_persistent_congestion; + /** + * :member:`on_ack_recv` is a callback function which is called when + * an acknowledgement is received. + */ + ngtcp2_cc_on_ack_recv on_ack_recv; + /** + * :member:`on_pkt_sent` is a callback function which is called when + * ack-eliciting packet is sent. + */ + ngtcp2_cc_on_pkt_sent on_pkt_sent; + /** + * :member:`new_rtt_sample` is a callback function which is called + * when new RTT sample is obtained. + */ + ngtcp2_cc_new_rtt_sample new_rtt_sample; + /** + * :member:`reset` is a callback function which is called when + * congestion control state must be reset. + */ + ngtcp2_cc_reset reset; + /** + * :member:`event` is a callback function which is called when a + * specific event happens. + */ + ngtcp2_cc_event event; +} ngtcp2_cc; + +/* + * ngtcp2_cc_compute_initcwnd computes initial cwnd. + */ +uint64_t ngtcp2_cc_compute_initcwnd(size_t max_packet_size); + +ngtcp2_cc_pkt *ngtcp2_cc_pkt_init(ngtcp2_cc_pkt *pkt, int64_t pkt_num, + size_t pktlen, ngtcp2_pktns_id pktns_id, + ngtcp2_tstamp sent_ts, uint64_t lost, + uint64_t tx_in_flight, int is_app_limited); + +/* ngtcp2_reno_cc is the RENO congestion controller. */ +typedef struct ngtcp2_reno_cc { + ngtcp2_cc_base ccb; + uint64_t max_delivery_rate_sec; + uint64_t target_cwnd; + uint64_t pending_add; +} ngtcp2_reno_cc; + +int ngtcp2_cc_reno_cc_init(ngtcp2_cc *cc, ngtcp2_log *log, + const ngtcp2_mem *mem); + +void ngtcp2_cc_reno_cc_free(ngtcp2_cc *cc, const ngtcp2_mem *mem); + +void ngtcp2_reno_cc_init(ngtcp2_reno_cc *cc, ngtcp2_log *log); + +void ngtcp2_reno_cc_free(ngtcp2_reno_cc *cc); + +void ngtcp2_cc_reno_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, ngtcp2_tstamp ts); + +void ngtcp2_cc_reno_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts); + +void ngtcp2_cc_reno_cc_on_persistent_congestion(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_reno_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); + +void ngtcp2_cc_reno_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/* ngtcp2_cubic_cc is CUBIC congestion controller. */ +typedef struct ngtcp2_cubic_cc { + ngtcp2_cc_base ccb; + uint64_t max_delivery_rate_sec; + uint64_t target_cwnd; + uint64_t w_last_max; + uint64_t w_tcp; + uint64_t origin_point; + ngtcp2_tstamp epoch_start; + uint64_t k; + /* prior stores the congestion state when a congestion event occurs + in order to restore the state when it turns out that the event is + spurious. */ + struct { + uint64_t cwnd; + uint64_t ssthresh; + uint64_t w_last_max; + uint64_t w_tcp; + uint64_t origin_point; + ngtcp2_tstamp epoch_start; + uint64_t k; + } prior; + /* HyStart++ variables */ + size_t rtt_sample_count; + uint64_t current_round_min_rtt; + uint64_t last_round_min_rtt; + int64_t window_end; + uint64_t pending_add; + uint64_t pending_w_add; +} ngtcp2_cubic_cc; + +int ngtcp2_cc_cubic_cc_init(ngtcp2_cc *cc, ngtcp2_log *log, + const ngtcp2_mem *mem); + +void ngtcp2_cc_cubic_cc_free(ngtcp2_cc *cc, const ngtcp2_mem *mem); + +void ngtcp2_cubic_cc_init(ngtcp2_cubic_cc *cc, ngtcp2_log *log); + +void ngtcp2_cubic_cc_free(ngtcp2_cubic_cc *cc); + +void ngtcp2_cc_cubic_cc_on_pkt_acked(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_congestion_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp sent_ts, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_on_spurious_congestion(ngtcp2_cc *ccx, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_on_persistent_congestion(ngtcp2_cc *cc, + ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_on_ack_recv(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_ack *ack, ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_on_pkt_sent(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_cc_pkt *pkt); + +void ngtcp2_cc_cubic_cc_new_rtt_sample(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_reset(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +void ngtcp2_cc_cubic_cc_event(ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + ngtcp2_cc_event_type event, ngtcp2_tstamp ts); + +#endif /* NGTCP2_CC_H */ diff --git a/lib/ngtcp2_cid.c b/lib/ngtcp2_cid.c new file mode 100644 index 0000000..f3b92b5 --- /dev/null +++ b/lib/ngtcp2_cid.c @@ -0,0 +1,147 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_cid.h" + +#include <assert.h> +#include <string.h> + +#include "ngtcp2_path.h" +#include "ngtcp2_str.h" + +void ngtcp2_cid_zero(ngtcp2_cid *cid) { memset(cid, 0, sizeof(*cid)); } + +void ngtcp2_cid_init(ngtcp2_cid *cid, const uint8_t *data, size_t datalen) { + assert(datalen <= NGTCP2_MAX_CIDLEN); + + cid->datalen = datalen; + if (datalen) { + ngtcp2_cpymem(cid->data, data, datalen); + } +} + +int ngtcp2_cid_eq(const ngtcp2_cid *cid, const ngtcp2_cid *other) { + return cid->datalen == other->datalen && + 0 == memcmp(cid->data, other->data, cid->datalen); +} + +int ngtcp2_cid_less(const ngtcp2_cid *lhs, const ngtcp2_cid *rhs) { + int s = lhs->datalen < rhs->datalen; + size_t n = s ? lhs->datalen : rhs->datalen; + int c = memcmp(lhs->data, rhs->data, n); + + return c < 0 || (c == 0 && s); +} + +int ngtcp2_cid_empty(const ngtcp2_cid *cid) { return cid->datalen == 0; } + +void ngtcp2_scid_init(ngtcp2_scid *scid, uint64_t seq, const ngtcp2_cid *cid) { + scid->pe.index = NGTCP2_PQ_BAD_INDEX; + scid->seq = seq; + scid->cid = *cid; + scid->retired_ts = UINT64_MAX; + scid->flags = NGTCP2_SCID_FLAG_NONE; +} + +void ngtcp2_scid_copy(ngtcp2_scid *dest, const ngtcp2_scid *src) { + ngtcp2_scid_init(dest, src->seq, &src->cid); + dest->retired_ts = src->retired_ts; + dest->flags = src->flags; +} + +void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid, + const uint8_t *token) { + dcid->seq = seq; + dcid->cid = *cid; + if (token) { + memcpy(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN); + dcid->flags = NGTCP2_DCID_FLAG_TOKEN_PRESENT; + } else { + dcid->flags = NGTCP2_DCID_FLAG_NONE; + } + ngtcp2_path_storage_zero(&dcid->ps); + dcid->retired_ts = UINT64_MAX; + dcid->bound_ts = UINT64_MAX; + dcid->bytes_sent = 0; + dcid->bytes_recv = 0; + dcid->max_udp_payload_size = NGTCP2_MAX_UDP_PAYLOAD_SIZE; +} + +void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, const uint8_t *token) { + assert(token); + + dcid->flags |= NGTCP2_DCID_FLAG_TOKEN_PRESENT; + memcpy(dcid->token, token, NGTCP2_STATELESS_RESET_TOKENLEN); +} + +void ngtcp2_dcid_set_path(ngtcp2_dcid *dcid, const ngtcp2_path *path) { + ngtcp2_path_copy(&dcid->ps.path, path); +} + +void ngtcp2_dcid_copy(ngtcp2_dcid *dest, const ngtcp2_dcid *src) { + ngtcp2_dcid_init(dest, src->seq, &src->cid, + (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) ? src->token + : NULL); + ngtcp2_path_copy(&dest->ps.path, &src->ps.path); + dest->retired_ts = src->retired_ts; + dest->bound_ts = src->bound_ts; + dest->flags = src->flags; + dest->bytes_sent = src->bytes_sent; + dest->bytes_recv = src->bytes_recv; + dest->max_udp_payload_size = src->max_udp_payload_size; +} + +void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src) { + dest->seq = src->seq; + dest->cid = src->cid; + if (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) { + dest->flags |= NGTCP2_DCID_FLAG_TOKEN_PRESENT; + memcpy(dest->token, src->token, NGTCP2_STATELESS_RESET_TOKENLEN); + } else if (dest->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) { + dest->flags &= (uint8_t)~NGTCP2_DCID_FLAG_TOKEN_PRESENT; + } +} + +int ngtcp2_dcid_verify_uniqueness(ngtcp2_dcid *dcid, uint64_t seq, + const ngtcp2_cid *cid, const uint8_t *token) { + if (dcid->seq == seq) { + return ngtcp2_cid_eq(&dcid->cid, cid) && + (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) && + memcmp(dcid->token, token, + NGTCP2_STATELESS_RESET_TOKENLEN) == 0 + ? 0 + : NGTCP2_ERR_PROTO; + } + + return !ngtcp2_cid_eq(&dcid->cid, cid) ? 0 : NGTCP2_ERR_PROTO; +} + +int ngtcp2_dcid_verify_stateless_reset_token(const ngtcp2_dcid *dcid, + const uint8_t *token) { + return (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) && + ngtcp2_cmemeq(dcid->token, token, + NGTCP2_STATELESS_RESET_TOKENLEN) + ? 0 + : NGTCP2_ERR_INVALID_ARGUMENT; +} diff --git a/lib/ngtcp2_cid.h b/lib/ngtcp2_cid.h new file mode 100644 index 0000000..0b37441 --- /dev/null +++ b/lib/ngtcp2_cid.h @@ -0,0 +1,175 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CID_H +#define NGTCP2_CID_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pq.h" +#include "ngtcp2_path.h" + +/* NGTCP2_SCID_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_SCID_FLAG_NONE 0x00u +/* NGTCP2_SCID_FLAG_USED indicates that a local endpoint observed that + a remote endpoint uses a particular Connection ID. */ +#define NGTCP2_SCID_FLAG_USED 0x01u +/* NGTCP2_SCID_FLAG_RETIRED indicates that a particular Connection ID + is retired. */ +#define NGTCP2_SCID_FLAG_RETIRED 0x02u + +typedef struct ngtcp2_scid { + ngtcp2_pq_entry pe; + /* seq is the sequence number associated to the CID. */ + uint64_t seq; + /* cid is a connection ID */ + ngtcp2_cid cid; + /* retired_ts is the timestamp when peer tells that this CID is + retired. */ + ngtcp2_tstamp retired_ts; + /* flags is the bitwise OR of zero or more of NGTCP2_SCID_FLAG_*. */ + uint8_t flags; +} ngtcp2_scid; + +/* NGTCP2_DCID_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_DCID_FLAG_NONE 0x00u +/* NGTCP2_DCID_FLAG_PATH_VALIDATED indicates that an associated path + has been validated. */ +#define NGTCP2_DCID_FLAG_PATH_VALIDATED 0x01u +/* NGTCP2_DCID_FLAG_TOKEN_PRESENT indicates that a stateless reset + token is set in token field. */ +#define NGTCP2_DCID_FLAG_TOKEN_PRESENT 0x02u + +typedef struct ngtcp2_dcid { + /* seq is the sequence number associated to the CID. */ + uint64_t seq; + /* cid is a connection ID */ + ngtcp2_cid cid; + /* path is a path which cid is bound to. The addresses are zero + length if cid has not been bound to a particular path yet. */ + ngtcp2_path_storage ps; + /* retired_ts is the timestamp when peer tells that this CID is + retired. */ + ngtcp2_tstamp retired_ts; + /* bound_ts is the timestamp when this connection ID is bound to a + particular path. It is only assigned when a connection ID is + used just for sending PATH_RESPONSE and is not zero-length. */ + ngtcp2_tstamp bound_ts; + /* bytes_sent is the number of bytes sent to an associated path. */ + uint64_t bytes_sent; + /* bytes_recv is the number of bytes received from an associated + path. */ + uint64_t bytes_recv; + /* max_udp_payload_size is the maximum size of UDP payload that is + allowed to send to this path. */ + size_t max_udp_payload_size; + /* flags is bitwise OR of zero or more of NGTCP2_DCID_FLAG_*. */ + uint8_t flags; + /* token is a stateless reset token associated to this CID. + Actually, the stateless reset token is tied to the connection, + not to the particular connection ID. */ + uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN]; +} ngtcp2_dcid; + +/* ngtcp2_cid_zero makes |cid| zero-length. */ +void ngtcp2_cid_zero(ngtcp2_cid *cid); + +/* + * ngtcp2_cid_less returns nonzero if |lhs| is lexicographical smaller + * than |rhs|. + */ +int ngtcp2_cid_less(const ngtcp2_cid *lhs, const ngtcp2_cid *rhs); + +/* + * ngtcp2_cid_empty returns nonzero if |cid| includes empty connection + * ID. + */ +int ngtcp2_cid_empty(const ngtcp2_cid *cid); + +/* + * ngtcp2_scid_init initializes |scid| with the given parameters. + */ +void ngtcp2_scid_init(ngtcp2_scid *scid, uint64_t seq, const ngtcp2_cid *cid); + +/* + * ngtcp2_scid_copy copies |src| into |dest|. + */ +void ngtcp2_scid_copy(ngtcp2_scid *dest, const ngtcp2_scid *src); + +/* + * ngtcp2_dcid_init initializes |dcid| with the given parameters. If + * |token| is NULL, the function fills dcid->token it with 0. |token| + * must be NGTCP2_STATELESS_RESET_TOKENLEN bytes long. + */ +void ngtcp2_dcid_init(ngtcp2_dcid *dcid, uint64_t seq, const ngtcp2_cid *cid, + const uint8_t *token); + +/* + * ngtcp2_dcid_set_token sets |token| to |dcid|. |token| must not be + * NULL and must be NGTCP2_STATELESS_RESET_TOKENLEN bytes long. + */ +void ngtcp2_dcid_set_token(ngtcp2_dcid *dcid, const uint8_t *token); + +/* + * ngtcp2_dcid_set_path sets |path| to |dcid|. It sets + * max_udp_payload_size to the minimum UDP payload size supported + * by the IP protocol version. + */ +void ngtcp2_dcid_set_path(ngtcp2_dcid *dcid, const ngtcp2_path *path); + +/* + * ngtcp2_dcid_copy copies |src| into |dest|. + */ +void ngtcp2_dcid_copy(ngtcp2_dcid *dest, const ngtcp2_dcid *src); + +/* + * ngtcp2_dcid_copy_cid_token behaves like ngtcp2_dcid_copy, but it + * only copies cid, seq, and path. + */ +void ngtcp2_dcid_copy_cid_token(ngtcp2_dcid *dest, const ngtcp2_dcid *src); + +/* + * ngtcp2_dcid_verify_uniqueness verifies uniqueness of (|seq|, |cid|, + * |token|) tuple against |dcid|. + */ +int ngtcp2_dcid_verify_uniqueness(ngtcp2_dcid *dcid, uint64_t seq, + const ngtcp2_cid *cid, const uint8_t *token); + +/* + * ngtcp2_dcid_verify_stateless_reset_token verifies stateless reset + * token |token| against the one included in |dcid|. This function + * returns 0 if the verification succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Tokens do not match; or |dcid| does not contain a token. + */ +int ngtcp2_dcid_verify_stateless_reset_token(const ngtcp2_dcid *dcid, + const uint8_t *token); + +#endif /* NGTCP2_CID_H */ diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c new file mode 100644 index 0000000..1bbda3f --- /dev/null +++ b/lib/ngtcp2_conn.c @@ -0,0 +1,13676 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_conn.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_macro.h" +#include "ngtcp2_log.h" +#include "ngtcp2_cid.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_addr.h" +#include "ngtcp2_path.h" +#include "ngtcp2_rcvry.h" +#include "ngtcp2_unreachable.h" +#include "ngtcp2_net.h" + +/* NGTCP2_FLOW_WINDOW_RTT_FACTOR is the factor of RTT when flow + control window auto-tuning is triggered. */ +#define NGTCP2_FLOW_WINDOW_RTT_FACTOR 2 +/* NGTCP2_FLOW_WINDOW_SCALING_FACTOR is the growth factor of flow + control window. */ +#define NGTCP2_FLOW_WINDOW_SCALING_FACTOR 2 +/* NGTCP2_MIN_COALESCED_PAYLOADLEN is the minimum length of QUIC + packet payload that should be coalesced to a long packet. */ +#define NGTCP2_MIN_COALESCED_PAYLOADLEN 128 + +/* + * conn_local_stream returns nonzero if |stream_id| indicates that it + * is the stream initiated by local endpoint. + */ +static int conn_local_stream(ngtcp2_conn *conn, int64_t stream_id) { + return (uint8_t)(stream_id & 1) == conn->server; +} + +/* + * bidi_stream returns nonzero if |stream_id| is a bidirectional + * stream ID. + */ +static int bidi_stream(int64_t stream_id) { return (stream_id & 0x2) == 0; } + +/* + * conn_is_handshake_completed returns nonzero if QUIC handshake has + * completed. + */ +static int conn_is_handshake_completed(ngtcp2_conn *conn) { + return (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED) && + conn->pktns.crypto.rx.ckm && conn->pktns.crypto.tx.ckm; +} + +static int conn_call_recv_client_initial(ngtcp2_conn *conn, + const ngtcp2_cid *dcid) { + int rv; + + assert(conn->callbacks.recv_client_initial); + + rv = conn->callbacks.recv_client_initial(conn, dcid, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_handshake_completed(ngtcp2_conn *conn) { + int rv; + + if (!conn->callbacks.handshake_completed) { + return 0; + } + + rv = conn->callbacks.handshake_completed(conn, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint32_t flags, uint64_t offset, + const uint8_t *data, size_t datalen) { + int rv; + + if (!conn->callbacks.recv_stream_data) { + return 0; + } + + rv = conn->callbacks.recv_stream_data(conn, flags, strm->stream_id, offset, + data, datalen, conn->user_data, + strm->stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, + size_t datalen) { + int rv; + + assert(conn->callbacks.recv_crypto_data); + + rv = conn->callbacks.recv_crypto_data(conn, crypto_level, offset, data, + datalen, conn->user_data); + switch (rv) { + case 0: + case NGTCP2_ERR_CRYPTO: + case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + case NGTCP2_ERR_TRANSPORT_PARAM: + case NGTCP2_ERR_PROTO: + case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE: + case NGTCP2_ERR_NOMEM: + case NGTCP2_ERR_CALLBACK_FAILURE: + return rv; + default: + return NGTCP2_ERR_CALLBACK_FAILURE; + } +} + +static int conn_call_stream_open(ngtcp2_conn *conn, ngtcp2_strm *strm) { + int rv; + + if (!conn->callbacks.stream_open) { + return 0; + } + + rv = conn->callbacks.stream_open(conn, strm->stream_id, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_stream_close(ngtcp2_conn *conn, ngtcp2_strm *strm) { + int rv; + uint32_t flags = NGTCP2_STREAM_CLOSE_FLAG_NONE; + + if (!conn->callbacks.stream_close) { + return 0; + } + + if (strm->flags & NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET) { + flags |= NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET; + } + + rv = conn->callbacks.stream_close(conn, flags, strm->stream_id, + strm->app_error_code, conn->user_data, + strm->stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_stream_reset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t final_size, uint64_t app_error_code, + void *stream_user_data) { + int rv; + + if (!conn->callbacks.stream_reset) { + return 0; + } + + rv = conn->callbacks.stream_reset(conn, stream_id, final_size, app_error_code, + conn->user_data, stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_local_streams_bidi(ngtcp2_conn *conn, + uint64_t max_streams) { + int rv; + + if (!conn->callbacks.extend_max_local_streams_bidi) { + return 0; + } + + rv = conn->callbacks.extend_max_local_streams_bidi(conn, max_streams, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_local_streams_uni(ngtcp2_conn *conn, + uint64_t max_streams) { + int rv; + + if (!conn->callbacks.extend_max_local_streams_uni) { + return 0; + } + + rv = conn->callbacks.extend_max_local_streams_uni(conn, max_streams, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen) { + int rv; + + assert(conn->callbacks.get_new_connection_id); + + rv = conn->callbacks.get_new_connection_id(conn, cid, token, cidlen, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_remove_connection_id(ngtcp2_conn *conn, + const ngtcp2_cid *cid) { + int rv; + + if (!conn->callbacks.remove_connection_id) { + return 0; + } + + rv = conn->callbacks.remove_connection_id(conn, cid, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_path_validation(ngtcp2_conn *conn, const ngtcp2_pv *pv, + ngtcp2_path_validation_result res) { + int rv; + uint32_t flags = NGTCP2_PATH_VALIDATION_FLAG_NONE; + + if (!conn->callbacks.path_validation) { + return 0; + } + + if (pv->flags & NGTCP2_PV_FLAG_PREFERRED_ADDR) { + flags |= NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR; + } + + rv = conn->callbacks.path_validation(conn, flags, &pv->dcid.ps.path, res, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_select_preferred_addr(ngtcp2_conn *conn, + ngtcp2_path *dest) { + int rv; + + if (!conn->callbacks.select_preferred_addr) { + return 0; + } + + assert(conn->remote.transport_params); + assert(conn->remote.transport_params->preferred_address_present); + + rv = conn->callbacks.select_preferred_addr( + conn, dest, &conn->remote.transport_params->preferred_address, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_remote_streams_bidi(ngtcp2_conn *conn, + uint64_t max_streams) { + int rv; + + if (!conn->callbacks.extend_max_remote_streams_bidi) { + return 0; + } + + rv = conn->callbacks.extend_max_remote_streams_bidi(conn, max_streams, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_remote_streams_uni(ngtcp2_conn *conn, + uint64_t max_streams) { + int rv; + + if (!conn->callbacks.extend_max_remote_streams_uni) { + return 0; + } + + rv = conn->callbacks.extend_max_remote_streams_uni(conn, max_streams, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_extend_max_stream_data(ngtcp2_conn *conn, + ngtcp2_strm *strm, + int64_t stream_id, + uint64_t datalen) { + int rv; + + if (!conn->callbacks.extend_max_stream_data) { + return 0; + } + + rv = conn->callbacks.extend_max_stream_data( + conn, stream_id, datalen, conn->user_data, strm->stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_dcid_status(ngtcp2_conn *conn, + ngtcp2_connection_id_status_type type, + const ngtcp2_dcid *dcid) { + int rv; + + if (!conn->callbacks.dcid_status) { + return 0; + } + + rv = conn->callbacks.dcid_status( + conn, (int)type, dcid->seq, &dcid->cid, + (dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) ? dcid->token : NULL, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_activate_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid) { + return conn_call_dcid_status(conn, NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE, + dcid); +} + +static int conn_call_deactivate_dcid(ngtcp2_conn *conn, + const ngtcp2_dcid *dcid) { + return conn_call_dcid_status( + conn, NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE, dcid); +} + +static int conn_call_stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code, + void *stream_user_data) { + int rv; + + if (!conn->callbacks.stream_stop_sending) { + return 0; + } + + rv = conn->callbacks.stream_stop_sending(conn, stream_id, app_error_code, + conn->user_data, stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static void conn_call_delete_crypto_aead_ctx(ngtcp2_conn *conn, + ngtcp2_crypto_aead_ctx *aead_ctx) { + if (!aead_ctx->native_handle) { + return; + } + + assert(conn->callbacks.delete_crypto_aead_ctx); + + conn->callbacks.delete_crypto_aead_ctx(conn, aead_ctx, conn->user_data); +} + +static void +conn_call_delete_crypto_cipher_ctx(ngtcp2_conn *conn, + ngtcp2_crypto_cipher_ctx *cipher_ctx) { + if (!cipher_ctx->native_handle) { + return; + } + + assert(conn->callbacks.delete_crypto_cipher_ctx); + + conn->callbacks.delete_crypto_cipher_ctx(conn, cipher_ctx, conn->user_data); +} + +static int conn_call_client_initial(ngtcp2_conn *conn) { + int rv; + + assert(conn->callbacks.client_initial); + + rv = conn->callbacks.client_initial(conn, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_get_path_challenge_data(ngtcp2_conn *conn, uint8_t *data) { + int rv; + + assert(conn->callbacks.get_path_challenge_data); + + rv = conn->callbacks.get_path_challenge_data(conn, data, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_version_negotiation(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv) { + int rv; + + if (!conn->callbacks.recv_version_negotiation) { + return 0; + } + + rv = conn->callbacks.recv_version_negotiation(conn, hd, sv, nsv, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd) { + int rv; + + assert(conn->callbacks.recv_retry); + + rv = conn->callbacks.recv_retry(conn, hd, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int +conn_call_recv_stateless_reset(ngtcp2_conn *conn, + const ngtcp2_pkt_stateless_reset *sr) { + int rv; + + if (!conn->callbacks.recv_stateless_reset) { + return 0; + } + + rv = conn->callbacks.recv_stateless_reset(conn, sr, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_new_token(ngtcp2_conn *conn, + const ngtcp2_vec *token) { + int rv; + + if (!conn->callbacks.recv_new_token) { + return 0; + } + + rv = conn->callbacks.recv_new_token(conn, token, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_handshake_confirmed(ngtcp2_conn *conn) { + int rv; + + if (!conn->callbacks.handshake_confirmed) { + return 0; + } + + rv = conn->callbacks.handshake_confirmed(conn, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_datagram(ngtcp2_conn *conn, + const ngtcp2_datagram *fr) { + const uint8_t *data; + size_t datalen; + int rv; + uint32_t flags = NGTCP2_DATAGRAM_FLAG_NONE; + + if (!conn->callbacks.recv_datagram) { + return 0; + } + + if (fr->datacnt) { + assert(fr->datacnt == 1); + + data = fr->data->base; + datalen = fr->data->len; + } else { + data = NULL; + datalen = 0; + } + + if (!conn_is_handshake_completed(conn)) { + flags |= NGTCP2_DATAGRAM_FLAG_EARLY; + } + + rv = conn->callbacks.recv_datagram(conn, flags, data, datalen, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int +conn_call_update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen) { + int rv; + + assert(conn->callbacks.update_key); + + rv = conn->callbacks.update_key( + conn, rx_secret, tx_secret, rx_aead_ctx, rx_iv, tx_aead_ctx, tx_iv, + current_rx_secret, current_tx_secret, secretlen, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_version_negotiation(ngtcp2_conn *conn, uint32_t version, + const ngtcp2_cid *dcid) { + int rv; + + assert(conn->callbacks.version_negotiation); + + rv = + conn->callbacks.version_negotiation(conn, version, dcid, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_rx_key(ngtcp2_conn *conn, ngtcp2_crypto_level level) { + int rv; + + if (!conn->callbacks.recv_rx_key) { + return 0; + } + + rv = conn->callbacks.recv_rx_key(conn, level, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int conn_call_recv_tx_key(ngtcp2_conn *conn, ngtcp2_crypto_level level) { + int rv; + + if (!conn->callbacks.recv_tx_key) { + return 0; + } + + rv = conn->callbacks.recv_tx_key(conn, level, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int crypto_offset_less(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + return *(int64_t *)lhs < *(int64_t *)rhs; +} + +static int pktns_init(ngtcp2_pktns *pktns, ngtcp2_pktns_id pktns_id, + ngtcp2_rst *rst, ngtcp2_cc *cc, ngtcp2_log *log, + ngtcp2_qlog *qlog, ngtcp2_objalloc *rtb_entry_objalloc, + ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem) { + int rv; + + memset(pktns, 0, sizeof(*pktns)); + + ngtcp2_gaptr_init(&pktns->rx.pngap, mem); + + pktns->tx.last_pkt_num = -1; + pktns->rx.max_pkt_num = -1; + pktns->rx.max_ack_eliciting_pkt_num = -1; + + rv = ngtcp2_acktr_init(&pktns->acktr, log, mem); + if (rv != 0) { + goto fail_acktr_init; + } + + ngtcp2_strm_init(&pktns->crypto.strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, + NULL, mem); + + ngtcp2_ksl_init(&pktns->crypto.tx.frq, crypto_offset_less, sizeof(uint64_t), + mem); + + ngtcp2_rtb_init(&pktns->rtb, pktns_id, &pktns->crypto.strm, rst, cc, log, + qlog, rtb_entry_objalloc, frc_objalloc, mem); + + return 0; + +fail_acktr_init: + ngtcp2_gaptr_free(&pktns->rx.pngap); + + return rv; +} + +static int pktns_new(ngtcp2_pktns **ppktns, ngtcp2_pktns_id pktns_id, + ngtcp2_rst *rst, ngtcp2_cc *cc, ngtcp2_log *log, + ngtcp2_qlog *qlog, ngtcp2_objalloc *rtb_entry_objalloc, + ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem) { + int rv; + + *ppktns = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pktns)); + if (*ppktns == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rv = pktns_init(*ppktns, pktns_id, rst, cc, log, qlog, rtb_entry_objalloc, + frc_objalloc, mem); + if (rv != 0) { + ngtcp2_mem_free(mem, *ppktns); + } + + return rv; +} + +static int cycle_less(const ngtcp2_pq_entry *lhs, const ngtcp2_pq_entry *rhs) { + ngtcp2_strm *ls = ngtcp2_struct_of(lhs, ngtcp2_strm, pe); + ngtcp2_strm *rs = ngtcp2_struct_of(rhs, ngtcp2_strm, pe); + + if (ls->cycle == rs->cycle) { + return ls->stream_id < rs->stream_id; + } + + return rs->cycle - ls->cycle <= 1; +} + +static void delete_buffed_pkts(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem) { + ngtcp2_pkt_chain *next; + + for (; pc;) { + next = pc->next; + ngtcp2_pkt_chain_del(pc, mem); + pc = next; + } +} + +static void delete_buf_chain(ngtcp2_buf_chain *bufchain, + const ngtcp2_mem *mem) { + ngtcp2_buf_chain *next; + + for (; bufchain;) { + next = bufchain->next; + ngtcp2_buf_chain_del(bufchain, mem); + bufchain = next; + } +} + +static void pktns_free(ngtcp2_pktns *pktns, const ngtcp2_mem *mem) { + ngtcp2_frame_chain *frc; + ngtcp2_ksl_it it; + + delete_buf_chain(pktns->crypto.tx.data, mem); + + delete_buffed_pkts(pktns->rx.buffed_pkts, mem); + + ngtcp2_frame_chain_list_objalloc_del(pktns->tx.frq, pktns->rtb.frc_objalloc, + mem); + + ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, mem); + ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, mem); + + for (it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + frc = ngtcp2_ksl_it_get(&it); + ngtcp2_frame_chain_objalloc_del(frc, pktns->rtb.frc_objalloc, mem); + } + + ngtcp2_ksl_free(&pktns->crypto.tx.frq); + ngtcp2_rtb_free(&pktns->rtb); + ngtcp2_strm_free(&pktns->crypto.strm); + ngtcp2_acktr_free(&pktns->acktr); + ngtcp2_gaptr_free(&pktns->rx.pngap); +} + +static void pktns_del(ngtcp2_pktns *pktns, const ngtcp2_mem *mem) { + if (pktns == NULL) { + return; + } + + pktns_free(pktns, mem); + + ngtcp2_mem_free(mem, pktns); +} + +static void cc_del(ngtcp2_cc *cc, ngtcp2_cc_algo cc_algo, + const ngtcp2_mem *mem) { + switch (cc_algo) { + case NGTCP2_CC_ALGO_RENO: + ngtcp2_cc_reno_cc_free(cc, mem); + break; + case NGTCP2_CC_ALGO_CUBIC: + ngtcp2_cc_cubic_cc_free(cc, mem); + break; + case NGTCP2_CC_ALGO_BBR: + ngtcp2_cc_bbr_cc_free(cc, mem); + break; + case NGTCP2_CC_ALGO_BBR2: + ngtcp2_cc_bbr2_cc_free(cc, mem); + break; + default: + break; + } +} + +static int cid_less(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) { + return ngtcp2_cid_less(lhs, rhs); +} + +static int retired_ts_less(const ngtcp2_pq_entry *lhs, + const ngtcp2_pq_entry *rhs) { + const ngtcp2_scid *a = ngtcp2_struct_of(lhs, ngtcp2_scid, pe); + const ngtcp2_scid *b = ngtcp2_struct_of(rhs, ngtcp2_scid, pe); + + return a->retired_ts < b->retired_ts; +} + +/* + * conn_reset_conn_stat_cc resets congestion state in |cstat|. + */ +static void conn_reset_conn_stat_cc(ngtcp2_conn *conn, + ngtcp2_conn_stat *cstat) { + cstat->latest_rtt = 0; + cstat->min_rtt = UINT64_MAX; + cstat->smoothed_rtt = conn->local.settings.initial_rtt; + cstat->rttvar = conn->local.settings.initial_rtt / 2; + cstat->first_rtt_sample_ts = UINT64_MAX; + cstat->pto_count = 0; + cstat->loss_detection_timer = UINT64_MAX; + cstat->cwnd = + ngtcp2_cc_compute_initcwnd(conn->local.settings.max_tx_udp_payload_size); + cstat->ssthresh = UINT64_MAX; + cstat->congestion_recovery_start_ts = UINT64_MAX; + cstat->bytes_in_flight = 0; + cstat->delivery_rate_sec = 0; + cstat->pacing_rate = 0.0; + cstat->send_quantum = 64 * 1024; +} + +/* + * reset_conn_stat_recovery resets the fields related to the recovery + * function + */ +static void reset_conn_stat_recovery(ngtcp2_conn_stat *cstat) { + /* Initializes them with UINT64_MAX. */ + memset(cstat->loss_time, 0xff, sizeof(cstat->loss_time)); + memset(cstat->last_tx_pkt_ts, 0xff, sizeof(cstat->last_tx_pkt_ts)); +} + +/* + * conn_reset_conn_stat resets |cstat|. The following fields are not + * reset: initial_rtt and max_udp_payload_size. + */ +static void conn_reset_conn_stat(ngtcp2_conn *conn, ngtcp2_conn_stat *cstat) { + conn_reset_conn_stat_cc(conn, cstat); + reset_conn_stat_recovery(cstat); +} + +static void delete_scid(ngtcp2_ksl *scids, const ngtcp2_mem *mem) { + ngtcp2_ksl_it it; + + for (it = ngtcp2_ksl_begin(scids); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_mem_free(mem, ngtcp2_ksl_it_get(&it)); + } +} + +/* + * compute_pto computes PTO. + */ +static ngtcp2_duration compute_pto(ngtcp2_duration smoothed_rtt, + ngtcp2_duration rttvar, + ngtcp2_duration max_ack_delay) { + ngtcp2_duration var = ngtcp2_max(4 * rttvar, NGTCP2_GRANULARITY); + return smoothed_rtt + var + max_ack_delay; +} + +/* + * conn_compute_initial_pto computes PTO using the initial RTT. + */ +static ngtcp2_duration conn_compute_initial_pto(ngtcp2_conn *conn, + ngtcp2_pktns *pktns) { + ngtcp2_duration initial_rtt = conn->local.settings.initial_rtt; + ngtcp2_duration max_ack_delay; + + if (pktns->rtb.pktns_id == NGTCP2_PKTNS_ID_APPLICATION && + conn->remote.transport_params) { + max_ack_delay = conn->remote.transport_params->max_ack_delay; + } else { + max_ack_delay = 0; + } + return compute_pto(initial_rtt, initial_rtt / 2, max_ack_delay); +} + +/* + * conn_compute_pto computes the current PTO. + */ +static ngtcp2_duration conn_compute_pto(ngtcp2_conn *conn, + ngtcp2_pktns *pktns) { + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_duration max_ack_delay; + + if (pktns->rtb.pktns_id == NGTCP2_PKTNS_ID_APPLICATION && + conn->remote.transport_params) { + max_ack_delay = conn->remote.transport_params->max_ack_delay; + } else { + max_ack_delay = 0; + } + return compute_pto(cstat->smoothed_rtt, cstat->rttvar, max_ack_delay); +} + +ngtcp2_duration ngtcp2_conn_compute_pto(ngtcp2_conn *conn, + ngtcp2_pktns *pktns) { + return conn_compute_pto(conn, pktns); +} + +static void conn_handle_tx_ecn(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint16_t *prtb_entry_flags, ngtcp2_pktns *pktns, + const ngtcp2_pkt_hd *hd, ngtcp2_tstamp ts) { + assert(pi); + + if (pi->ecn != NGTCP2_ECN_NOT_ECT) { + /* We have already made a transition of validation state and + deceided to send UDP datagram with ECN bit set. Coalesced QUIC + packets also bear ECN bits set. */ + if (pktns->tx.ecn.start_pkt_num == INT64_MAX) { + pktns->tx.ecn.start_pkt_num = hd->pkt_num; + } + + ++pktns->tx.ecn.validation_pkt_sent; + + if (prtb_entry_flags) { + *prtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ECN; + } + + ++pktns->tx.ecn.ect0; + + return; + } + + switch (conn->tx.ecn.state) { + case NGTCP2_ECN_STATE_TESTING: + if (conn->tx.ecn.validation_start_ts == UINT64_MAX) { + assert(0 == pktns->tx.ecn.validation_pkt_sent); + assert(0 == pktns->tx.ecn.validation_pkt_lost); + + conn->tx.ecn.validation_start_ts = ts; + } else if (ts - conn->tx.ecn.validation_start_ts >= + 3 * conn_compute_pto(conn, pktns)) { + conn->tx.ecn.state = NGTCP2_ECN_STATE_UNKNOWN; + break; + } + + if (pktns->tx.ecn.start_pkt_num == INT64_MAX) { + pktns->tx.ecn.start_pkt_num = hd->pkt_num; + } + + ++pktns->tx.ecn.validation_pkt_sent; + + if (++conn->tx.ecn.dgram_sent == NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS) { + conn->tx.ecn.state = NGTCP2_ECN_STATE_UNKNOWN; + } + /* fall through */ + case NGTCP2_ECN_STATE_CAPABLE: + /* pi is provided per UDP datagram. */ + assert(NGTCP2_ECN_NOT_ECT == pi->ecn); + + pi->ecn = NGTCP2_ECN_ECT_0; + + if (prtb_entry_flags) { + *prtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ECN; + } + + ++pktns->tx.ecn.ect0; + break; + case NGTCP2_ECN_STATE_UNKNOWN: + case NGTCP2_ECN_STATE_FAILED: + break; + default: + ngtcp2_unreachable(); + } +} + +static void conn_reset_ecn_validation_state(ngtcp2_conn *conn) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_pktns *pktns = &conn->pktns; + + conn->tx.ecn.state = NGTCP2_ECN_STATE_TESTING; + conn->tx.ecn.validation_start_ts = UINT64_MAX; + conn->tx.ecn.dgram_sent = 0; + + if (in_pktns) { + in_pktns->tx.ecn.start_pkt_num = INT64_MAX; + in_pktns->tx.ecn.validation_pkt_sent = 0; + in_pktns->tx.ecn.validation_pkt_lost = 0; + } + + if (hs_pktns) { + hs_pktns->tx.ecn.start_pkt_num = INT64_MAX; + hs_pktns->tx.ecn.validation_pkt_sent = 0; + hs_pktns->tx.ecn.validation_pkt_lost = 0; + } + + pktns->tx.ecn.start_pkt_num = INT64_MAX; + pktns->tx.ecn.validation_pkt_sent = 0; + pktns->tx.ecn.validation_pkt_lost = 0; +} + +/* server_default_other_versions is the default other_versions field + sent by server. */ +static uint8_t server_default_other_versions[] = {0, 0, 0, 1}; + +/* + * other_versions_new allocates new buffer, and writes |versions| of + * length |versionslen| in network byte order, suitable for sending in + * other_versions field of version_information QUIC transport + * parameter. The pointer to the allocated buffer is assigned to + * |*pbuf|. + * + * This function returns 0 if it succeeds, or one of the negative + * error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int other_versions_new(uint8_t **pbuf, const uint32_t *versions, + size_t versionslen, const ngtcp2_mem *mem) { + size_t i; + uint8_t *buf = ngtcp2_mem_malloc(mem, sizeof(uint32_t) * versionslen); + + if (buf == NULL) { + return NGTCP2_ERR_NOMEM; + } + + *pbuf = buf; + + for (i = 0; i < versionslen; ++i) { + buf = ngtcp2_put_uint32be(buf, versions[i]); + } + + return 0; +} + +static void +conn_set_local_transport_params(ngtcp2_conn *conn, + const ngtcp2_transport_params *params) { + ngtcp2_transport_params *p = &conn->local.transport_params; + uint32_t chosen_version = p->version_info.chosen_version; + + *p = *params; + + /* grease_quic_bit is always enabled. */ + p->grease_quic_bit = 1; + + if (conn->server) { + p->version_info.chosen_version = chosen_version; + } else { + p->version_info.chosen_version = conn->client_chosen_version; + } + p->version_info.other_versions = conn->vneg.other_versions; + p->version_info.other_versionslen = conn->vneg.other_versionslen; + p->version_info_present = 1; +} + +static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_path *path, + uint32_t client_chosen_version, int callbacks_version, + const ngtcp2_callbacks *callbacks, int settings_version, + const ngtcp2_settings *settings, + int transport_params_version, + const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data, int server) { + int rv; + ngtcp2_scid *scident; + uint8_t *buf; + uint8_t fixed_bit_byte; + size_t i; + uint32_t *preferred_versions; + (void)callbacks_version; + (void)settings_version; + (void)transport_params_version; + + assert(settings->max_window <= NGTCP2_MAX_VARINT); + assert(settings->max_stream_window <= NGTCP2_MAX_VARINT); + assert(settings->max_tx_udp_payload_size); + assert(settings->max_tx_udp_payload_size <= NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE); + assert(params->active_connection_id_limit <= NGTCP2_MAX_DCID_POOL_SIZE); + assert(params->initial_max_data <= NGTCP2_MAX_VARINT); + assert(params->initial_max_stream_data_bidi_local <= NGTCP2_MAX_VARINT); + assert(params->initial_max_stream_data_bidi_remote <= NGTCP2_MAX_VARINT); + assert(params->initial_max_stream_data_uni <= NGTCP2_MAX_VARINT); + assert(server || callbacks->client_initial); + assert(!server || callbacks->recv_client_initial); + assert(callbacks->recv_crypto_data); + assert(callbacks->encrypt); + assert(callbacks->decrypt); + assert(callbacks->hp_mask); + assert(server || callbacks->recv_retry); + assert(callbacks->rand); + assert(callbacks->get_new_connection_id); + assert(callbacks->update_key); + assert(callbacks->delete_crypto_aead_ctx); + assert(callbacks->delete_crypto_cipher_ctx); + assert(callbacks->get_path_challenge_data); + assert(!server || !ngtcp2_is_reserved_version(client_chosen_version)); + + if (mem == NULL) { + mem = ngtcp2_mem_default(); + } + + *pconn = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_conn)); + if (*pconn == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_conn; + } + + ngtcp2_objalloc_frame_chain_init(&(*pconn)->frc_objalloc, 64, mem); + ngtcp2_objalloc_rtb_entry_init(&(*pconn)->rtb_entry_objalloc, 64, mem); + ngtcp2_objalloc_strm_init(&(*pconn)->strm_objalloc, 64, mem); + + ngtcp2_static_ringbuf_dcid_bound_init(&(*pconn)->dcid.bound); + + ngtcp2_static_ringbuf_dcid_unused_init(&(*pconn)->dcid.unused); + + ngtcp2_static_ringbuf_dcid_retired_init(&(*pconn)->dcid.retired); + + ngtcp2_gaptr_init(&(*pconn)->dcid.seqgap, mem); + + ngtcp2_ksl_init(&(*pconn)->scid.set, cid_less, sizeof(ngtcp2_cid), mem); + + ngtcp2_pq_init(&(*pconn)->scid.used, retired_ts_less, mem); + + ngtcp2_map_init(&(*pconn)->strms, mem); + + ngtcp2_pq_init(&(*pconn)->tx.strmq, cycle_less, mem); + + ngtcp2_idtr_init(&(*pconn)->remote.bidi.idtr, !server, mem); + + ngtcp2_idtr_init(&(*pconn)->remote.uni.idtr, !server, mem); + + ngtcp2_static_ringbuf_path_challenge_init(&(*pconn)->rx.path_challenge); + + ngtcp2_log_init(&(*pconn)->log, scid, settings->log_printf, + settings->initial_ts, user_data); + ngtcp2_qlog_init(&(*pconn)->qlog, settings->qlog.write, settings->initial_ts, + user_data); + if ((*pconn)->qlog.write) { + buf = ngtcp2_mem_malloc(mem, NGTCP2_QLOG_BUFLEN); + if (buf == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_qlog_buf; + } + ngtcp2_buf_init(&(*pconn)->qlog.buf, buf, NGTCP2_QLOG_BUFLEN); + } + + (*pconn)->local.settings = *settings; + + if (settings->token.len) { + buf = ngtcp2_mem_malloc(mem, settings->token.len); + if (buf == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_token; + } + memcpy(buf, settings->token.base, settings->token.len); + (*pconn)->local.settings.token.base = buf; + } else { + (*pconn)->local.settings.token.base = NULL; + (*pconn)->local.settings.token.len = 0; + } + + if (!(*pconn)->local.settings.original_version) { + (*pconn)->local.settings.original_version = client_chosen_version; + } + + conn_reset_conn_stat(*pconn, &(*pconn)->cstat); + (*pconn)->cstat.initial_rtt = settings->initial_rtt; + (*pconn)->cstat.max_tx_udp_payload_size = + (*pconn)->local.settings.max_tx_udp_payload_size; + + ngtcp2_rst_init(&(*pconn)->rst); + + (*pconn)->cc_algo = settings->cc_algo; + + switch (settings->cc_algo) { + case NGTCP2_CC_ALGO_RENO: + rv = ngtcp2_cc_reno_cc_init(&(*pconn)->cc, &(*pconn)->log, mem); + if (rv != 0) { + goto fail_cc_init; + } + break; + case NGTCP2_CC_ALGO_CUBIC: + rv = ngtcp2_cc_cubic_cc_init(&(*pconn)->cc, &(*pconn)->log, mem); + if (rv != 0) { + goto fail_cc_init; + } + break; + case NGTCP2_CC_ALGO_BBR: + rv = ngtcp2_cc_bbr_cc_init(&(*pconn)->cc, &(*pconn)->log, &(*pconn)->cstat, + &(*pconn)->rst, settings->initial_ts, + callbacks->rand, &settings->rand_ctx, mem); + if (rv != 0) { + goto fail_cc_init; + } + break; + case NGTCP2_CC_ALGO_BBR2: + rv = ngtcp2_cc_bbr2_cc_init(&(*pconn)->cc, &(*pconn)->log, &(*pconn)->cstat, + &(*pconn)->rst, settings->initial_ts, + callbacks->rand, &settings->rand_ctx, mem); + if (rv != 0) { + goto fail_cc_init; + } + break; + default: + ngtcp2_unreachable(); + } + + rv = pktns_new(&(*pconn)->in_pktns, NGTCP2_PKTNS_ID_INITIAL, &(*pconn)->rst, + &(*pconn)->cc, &(*pconn)->log, &(*pconn)->qlog, + &(*pconn)->rtb_entry_objalloc, &(*pconn)->frc_objalloc, mem); + if (rv != 0) { + goto fail_in_pktns_init; + } + + rv = pktns_new(&(*pconn)->hs_pktns, NGTCP2_PKTNS_ID_HANDSHAKE, &(*pconn)->rst, + &(*pconn)->cc, &(*pconn)->log, &(*pconn)->qlog, + &(*pconn)->rtb_entry_objalloc, &(*pconn)->frc_objalloc, mem); + if (rv != 0) { + goto fail_hs_pktns_init; + } + + rv = pktns_init(&(*pconn)->pktns, NGTCP2_PKTNS_ID_APPLICATION, &(*pconn)->rst, + &(*pconn)->cc, &(*pconn)->log, &(*pconn)->qlog, + &(*pconn)->rtb_entry_objalloc, &(*pconn)->frc_objalloc, mem); + if (rv != 0) { + goto fail_pktns_init; + } + + scident = ngtcp2_mem_malloc(mem, sizeof(*scident)); + if (scident == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_scident; + } + + /* Set stateless reset token later if it is available in the local + transport parameters */ + ngtcp2_scid_init(scident, 0, scid); + + rv = ngtcp2_ksl_insert(&(*pconn)->scid.set, NULL, &scident->cid, scident); + if (rv != 0) { + goto fail_scid_set_insert; + } + + scident = NULL; + + ngtcp2_dcid_init(&(*pconn)->dcid.current, 0, dcid, NULL); + ngtcp2_dcid_set_path(&(*pconn)->dcid.current, path); + + rv = ngtcp2_gaptr_push(&(*pconn)->dcid.seqgap, 0, 1); + if (rv != 0) { + goto fail_seqgap_push; + } + + if (settings->preferred_versionslen) { + if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) { + for (i = 0; i < settings->preferred_versionslen; ++i) { + if (settings->preferred_versions[i] == client_chosen_version) { + break; + } + } + + assert(i < settings->preferred_versionslen); + } + + preferred_versions = ngtcp2_mem_malloc( + mem, sizeof(uint32_t) * settings->preferred_versionslen); + if (preferred_versions == NULL) { + rv = NGTCP2_ERR_NOMEM; + goto fail_preferred_versions; + } + + for (i = 0; i < settings->preferred_versionslen; ++i) { + assert(ngtcp2_is_supported_version(settings->preferred_versions[i])); + + preferred_versions[i] = settings->preferred_versions[i]; + } + + (*pconn)->vneg.preferred_versions = preferred_versions; + (*pconn)->vneg.preferred_versionslen = settings->preferred_versionslen; + } + + if (settings->other_versionslen) { + if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) { + for (i = 0; i < settings->other_versionslen; ++i) { + if (settings->other_versions[i] == client_chosen_version) { + break; + } + } + + assert(i < settings->other_versionslen); + } + + for (i = 0; i < settings->other_versionslen; ++i) { + assert(ngtcp2_is_reserved_version(settings->other_versions[i]) || + ngtcp2_is_supported_version(settings->other_versions[i])); + } + + rv = other_versions_new(&buf, settings->other_versions, + settings->other_versionslen, mem); + if (rv != 0) { + goto fail_other_versions; + } + + (*pconn)->vneg.other_versions = buf; + (*pconn)->vneg.other_versionslen = + sizeof(uint32_t) * settings->other_versionslen; + } else if (server) { + if (settings->preferred_versionslen) { + rv = other_versions_new(&buf, settings->preferred_versions, + settings->preferred_versionslen, mem); + if (rv != 0) { + goto fail_other_versions; + } + + (*pconn)->vneg.other_versions = buf; + (*pconn)->vneg.other_versionslen = + sizeof(uint32_t) * settings->preferred_versionslen; + } else { + (*pconn)->vneg.other_versions = server_default_other_versions; + (*pconn)->vneg.other_versionslen = sizeof(server_default_other_versions); + } + } else if (!server && !ngtcp2_is_reserved_version(client_chosen_version)) { + rv = other_versions_new(&buf, &client_chosen_version, 1, mem); + if (rv != 0) { + goto fail_other_versions; + } + + (*pconn)->vneg.other_versions = buf; + (*pconn)->vneg.other_versionslen = sizeof(uint32_t); + } + + (*pconn)->client_chosen_version = client_chosen_version; + + conn_set_local_transport_params(*pconn, params); + + callbacks->rand(&fixed_bit_byte, 1, &settings->rand_ctx); + if (fixed_bit_byte & 1) { + (*pconn)->flags |= NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT; + } + + (*pconn)->keep_alive.last_ts = UINT64_MAX; + + (*pconn)->server = server; + (*pconn)->oscid = *scid; + (*pconn)->callbacks = *callbacks; + (*pconn)->mem = mem; + (*pconn)->user_data = user_data; + (*pconn)->idle_ts = settings->initial_ts; + (*pconn)->crypto.key_update.confirmed_ts = UINT64_MAX; + (*pconn)->tx.last_max_data_ts = UINT64_MAX; + (*pconn)->tx.pacing.next_ts = UINT64_MAX; + (*pconn)->early.discard_started_ts = UINT64_MAX; + + conn_reset_ecn_validation_state(*pconn); + + ngtcp2_qlog_start(&(*pconn)->qlog, server ? &settings->qlog.odcid : dcid, + server); + + return 0; + +fail_other_versions: + ngtcp2_mem_free(mem, (*pconn)->vneg.preferred_versions); +fail_preferred_versions: +fail_seqgap_push: +fail_scid_set_insert: + ngtcp2_mem_free(mem, scident); +fail_scident: + pktns_free(&(*pconn)->pktns, mem); +fail_pktns_init: + pktns_del((*pconn)->hs_pktns, mem); +fail_hs_pktns_init: + pktns_del((*pconn)->in_pktns, mem); +fail_in_pktns_init: + cc_del(&(*pconn)->cc, settings->cc_algo, mem); +fail_cc_init: + ngtcp2_mem_free(mem, (*pconn)->local.settings.token.base); +fail_token: + ngtcp2_mem_free(mem, (*pconn)->qlog.buf.begin); +fail_qlog_buf: + ngtcp2_idtr_free(&(*pconn)->remote.uni.idtr); + ngtcp2_idtr_free(&(*pconn)->remote.bidi.idtr); + ngtcp2_map_free(&(*pconn)->strms); + delete_scid(&(*pconn)->scid.set, mem); + ngtcp2_ksl_free(&(*pconn)->scid.set); + ngtcp2_gaptr_free(&(*pconn)->dcid.seqgap); + ngtcp2_objalloc_free(&(*pconn)->strm_objalloc); + ngtcp2_objalloc_free(&(*pconn)->rtb_entry_objalloc); + ngtcp2_objalloc_free(&(*pconn)->frc_objalloc); + ngtcp2_mem_free(mem, *pconn); +fail_conn: + return rv; +} + +int ngtcp2_conn_client_new_versioned( + ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_path *path, uint32_t client_chosen_version, + int callbacks_version, const ngtcp2_callbacks *callbacks, + int settings_version, const ngtcp2_settings *settings, + int transport_params_version, const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data) { + int rv; + + rv = conn_new(pconn, dcid, scid, path, client_chosen_version, + callbacks_version, callbacks, settings_version, settings, + transport_params_version, params, mem, user_data, 0); + if (rv != 0) { + return rv; + } + (*pconn)->rcid = *dcid; + (*pconn)->state = NGTCP2_CS_CLIENT_INITIAL; + (*pconn)->local.bidi.next_stream_id = 0; + (*pconn)->local.uni.next_stream_id = 2; + + rv = ngtcp2_conn_commit_local_transport_params(*pconn); + if (rv != 0) { + ngtcp2_conn_del(*pconn); + return rv; + } + + return 0; +} + +int ngtcp2_conn_server_new_versioned( + ngtcp2_conn **pconn, const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + const ngtcp2_path *path, uint32_t client_chosen_version, + int callbacks_version, const ngtcp2_callbacks *callbacks, + int settings_version, const ngtcp2_settings *settings, + int transport_params_version, const ngtcp2_transport_params *params, + const ngtcp2_mem *mem, void *user_data) { + int rv; + + rv = conn_new(pconn, dcid, scid, path, client_chosen_version, + callbacks_version, callbacks, settings_version, settings, + transport_params_version, params, mem, user_data, 1); + if (rv != 0) { + return rv; + } + + (*pconn)->state = NGTCP2_CS_SERVER_INITIAL; + (*pconn)->local.bidi.next_stream_id = 1; + (*pconn)->local.uni.next_stream_id = 3; + + if ((*pconn)->local.settings.token.len) { + /* Usage of token lifts amplification limit */ + (*pconn)->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + } + + return 0; +} + +/* + * conn_fc_credits returns the number of bytes allowed to be sent to + * the given stream. Both connection and stream level flow control + * credits are considered. + */ +static uint64_t conn_fc_credits(ngtcp2_conn *conn, ngtcp2_strm *strm) { + return ngtcp2_min(strm->tx.max_offset - strm->tx.offset, + conn->tx.max_offset - conn->tx.offset); +} + +/* + * conn_enforce_flow_control returns the number of bytes allowed to be + * sent to the given stream. |len| might be shorted because of + * available flow control credits. + */ +static uint64_t conn_enforce_flow_control(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t len) { + uint64_t fc_credits = conn_fc_credits(conn, strm); + return ngtcp2_min(len, fc_credits); +} + +static int delete_strms_each(void *data, void *ptr) { + ngtcp2_conn *conn = ptr; + ngtcp2_strm *s = data; + + ngtcp2_strm_free(s); + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, s); + + return 0; +} + +static void conn_vneg_crypto_free(ngtcp2_conn *conn) { + if (conn->vneg.rx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.rx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.rx.hp_ctx); + + if (conn->vneg.tx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.tx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.tx.hp_ctx); + + ngtcp2_crypto_km_del(conn->vneg.rx.ckm, conn->mem); + ngtcp2_crypto_km_del(conn->vneg.tx.ckm, conn->mem); +} + +void ngtcp2_conn_del(ngtcp2_conn *conn) { + if (conn == NULL) { + return; + } + + ngtcp2_qlog_end(&conn->qlog); + + if (conn->early.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->early.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->early.hp_ctx); + + if (conn->crypto.key_update.old_rx_ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->crypto.key_update.old_rx_ckm->aead_ctx); + } + if (conn->crypto.key_update.new_rx_ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->crypto.key_update.new_rx_ckm->aead_ctx); + } + if (conn->crypto.key_update.new_tx_ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->crypto.key_update.new_tx_ckm->aead_ctx); + } + + if (conn->pktns.crypto.rx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, + &conn->pktns.crypto.rx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->pktns.crypto.rx.hp_ctx); + + if (conn->pktns.crypto.tx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, + &conn->pktns.crypto.tx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->pktns.crypto.tx.hp_ctx); + + if (conn->hs_pktns) { + if (conn->hs_pktns->crypto.rx.ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->hs_pktns->crypto.rx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->hs_pktns->crypto.rx.hp_ctx); + + if (conn->hs_pktns->crypto.tx.ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->hs_pktns->crypto.tx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->hs_pktns->crypto.tx.hp_ctx); + } + if (conn->in_pktns) { + if (conn->in_pktns->crypto.rx.ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->in_pktns->crypto.rx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->in_pktns->crypto.rx.hp_ctx); + + if (conn->in_pktns->crypto.tx.ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->in_pktns->crypto.tx.ckm->aead_ctx); + } + conn_call_delete_crypto_cipher_ctx(conn, &conn->in_pktns->crypto.tx.hp_ctx); + } + + conn_call_delete_crypto_aead_ctx(conn, &conn->crypto.retry_aead_ctx); + + ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem); + ngtcp2_transport_params_del(conn->remote.pending_transport_params, conn->mem); + + conn_vneg_crypto_free(conn); + + ngtcp2_mem_free(conn->mem, conn->vneg.preferred_versions); + if (conn->vneg.other_versions != server_default_other_versions) { + ngtcp2_mem_free(conn->mem, conn->vneg.other_versions); + } + + ngtcp2_mem_free(conn->mem, conn->crypto.decrypt_buf.base); + ngtcp2_mem_free(conn->mem, conn->crypto.decrypt_hp_buf.base); + ngtcp2_mem_free(conn->mem, conn->local.settings.token.base); + + ngtcp2_crypto_km_del(conn->crypto.key_update.old_rx_ckm, conn->mem); + ngtcp2_crypto_km_del(conn->crypto.key_update.new_rx_ckm, conn->mem); + ngtcp2_crypto_km_del(conn->crypto.key_update.new_tx_ckm, conn->mem); + ngtcp2_crypto_km_del(conn->early.ckm, conn->mem); + + pktns_free(&conn->pktns, conn->mem); + pktns_del(conn->hs_pktns, conn->mem); + pktns_del(conn->in_pktns, conn->mem); + + cc_del(&conn->cc, conn->cc_algo, conn->mem); + + ngtcp2_mem_free(conn->mem, conn->qlog.buf.begin); + + ngtcp2_pmtud_del(conn->pmtud); + ngtcp2_pv_del(conn->pv); + + ngtcp2_mem_free(conn->mem, conn->rx.ccerr.reason); + + ngtcp2_idtr_free(&conn->remote.uni.idtr); + ngtcp2_idtr_free(&conn->remote.bidi.idtr); + ngtcp2_mem_free(conn->mem, conn->tx.ack); + ngtcp2_pq_free(&conn->tx.strmq); + ngtcp2_map_each_free(&conn->strms, delete_strms_each, (void *)conn); + ngtcp2_map_free(&conn->strms); + + ngtcp2_pq_free(&conn->scid.used); + delete_scid(&conn->scid.set, conn->mem); + ngtcp2_ksl_free(&conn->scid.set); + ngtcp2_gaptr_free(&conn->dcid.seqgap); + + ngtcp2_objalloc_free(&conn->strm_objalloc); + ngtcp2_objalloc_free(&conn->rtb_entry_objalloc); + ngtcp2_objalloc_free(&conn->frc_objalloc); + + ngtcp2_mem_free(conn->mem, conn); +} + +/* + * conn_ensure_ack_ranges makes sure that conn->tx.ack->ack.ranges can + * contain at least |n| additional ngtcp2_ack_range. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_ensure_ack_ranges(ngtcp2_conn *conn, size_t n) { + ngtcp2_frame *fr; + size_t max = conn->tx.max_ack_ranges; + + if (n <= max) { + return 0; + } + + max *= 2; + + assert(max >= n); + + fr = ngtcp2_mem_realloc(conn->mem, conn->tx.ack, + sizeof(ngtcp2_ack) + sizeof(ngtcp2_ack_range) * max); + if (fr == NULL) { + return NGTCP2_ERR_NOMEM; + } + + conn->tx.ack = fr; + conn->tx.max_ack_ranges = max; + + return 0; +} + +/* + * conn_compute_ack_delay computes ACK delay for outgoing protected + * ACK. + */ +static ngtcp2_duration conn_compute_ack_delay(ngtcp2_conn *conn) { + return ngtcp2_min(conn->local.transport_params.max_ack_delay, + conn->cstat.smoothed_rtt / 8); +} + +/* + * conn_create_ack_frame creates ACK frame, and assigns its pointer to + * |*pfr| if there are any received packets to acknowledge. If there + * are no packets to acknowledge, this function returns 0, and |*pfr| + * is untouched. The caller is advised to set |*pfr| to NULL before + * calling this function, and check it after this function returns. + * If |nodelay| is nonzero, delayed ACK timer is ignored. + * + * The memory for ACK frame is dynamically allocated by this function. + * A caller is responsible to free it. + * + * Call ngtcp2_acktr_commit_ack after a created ACK frame is + * successfully serialized into a packet. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_create_ack_frame(ngtcp2_conn *conn, ngtcp2_frame **pfr, + ngtcp2_pktns *pktns, uint8_t type, + ngtcp2_tstamp ts, ngtcp2_duration ack_delay, + uint64_t ack_delay_exponent) { + /* TODO Measure an actual size of ACK blocks to find the best + default value. */ + const size_t initial_max_ack_ranges = 8; + int64_t last_pkt_num; + ngtcp2_acktr *acktr = &pktns->acktr; + ngtcp2_ack_range *range; + ngtcp2_ksl_it it; + ngtcp2_acktr_entry *rpkt; + ngtcp2_ack *ack; + size_t range_idx; + ngtcp2_tstamp largest_ack_ts; + int rv; + + if (acktr->flags & NGTCP2_ACKTR_FLAG_IMMEDIATE_ACK) { + ack_delay = 0; + } + + if (!ngtcp2_acktr_require_active_ack(acktr, ack_delay, ts)) { + return 0; + } + + it = ngtcp2_acktr_get(acktr); + if (ngtcp2_ksl_it_end(&it)) { + ngtcp2_acktr_commit_ack(acktr); + return 0; + } + + if (conn->tx.ack == NULL) { + conn->tx.ack = ngtcp2_mem_malloc( + conn->mem, + sizeof(ngtcp2_ack) + sizeof(ngtcp2_ack_range) * initial_max_ack_ranges); + if (conn->tx.ack == NULL) { + return NGTCP2_ERR_NOMEM; + } + conn->tx.max_ack_ranges = initial_max_ack_ranges; + } + + ack = &conn->tx.ack->ack; + + if (pktns->rx.ecn.ect0 || pktns->rx.ecn.ect1 || pktns->rx.ecn.ce) { + ack->type = NGTCP2_FRAME_ACK_ECN; + ack->ecn.ect0 = pktns->rx.ecn.ect0; + ack->ecn.ect1 = pktns->rx.ecn.ect1; + ack->ecn.ce = pktns->rx.ecn.ce; + } else { + ack->type = NGTCP2_FRAME_ACK; + } + ack->rangecnt = 0; + + rpkt = ngtcp2_ksl_it_get(&it); + + if (rpkt->pkt_num == pktns->rx.max_pkt_num) { + last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); + largest_ack_ts = rpkt->tstamp; + ack->largest_ack = rpkt->pkt_num; + ack->first_ack_range = rpkt->len - 1; + + ngtcp2_ksl_it_next(&it); + } else { + assert(rpkt->pkt_num < pktns->rx.max_pkt_num); + + last_pkt_num = pktns->rx.max_pkt_num; + largest_ack_ts = pktns->rx.max_pkt_ts; + ack->largest_ack = pktns->rx.max_pkt_num; + ack->first_ack_range = 0; + } + + if (type == NGTCP2_PKT_1RTT) { + ack->ack_delay_unscaled = ts - largest_ack_ts; + ack->ack_delay = ack->ack_delay_unscaled / NGTCP2_MICROSECONDS / + (1ULL << ack_delay_exponent); + } else { + ack->ack_delay_unscaled = 0; + ack->ack_delay = 0; + } + + for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { + if (ack->rangecnt == NGTCP2_MAX_ACK_RANGES) { + break; + } + + rpkt = ngtcp2_ksl_it_get(&it); + + range_idx = ack->rangecnt++; + rv = conn_ensure_ack_ranges(conn, ack->rangecnt); + if (rv != 0) { + return rv; + } + ack = &conn->tx.ack->ack; + range = &ack->ranges[range_idx]; + range->gap = (uint64_t)(last_pkt_num - rpkt->pkt_num - 2); + range->len = rpkt->len - 1; + + last_pkt_num = rpkt->pkt_num - (int64_t)(rpkt->len - 1); + } + + /* TODO Just remove entries which cannot fit into a single ACK frame + for now. */ + if (!ngtcp2_ksl_it_end(&it)) { + ngtcp2_acktr_forget(acktr, ngtcp2_ksl_it_get(&it)); + } + + *pfr = conn->tx.ack; + + return 0; +} + +/* + * conn_ppe_write_frame writes |fr| to |ppe|. If |hd_logged| is not + * NULL and |*hd_logged| is zero, packet header is logged, and 1 is + * assigned to |*hd_logged|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer is too small. + */ +static int conn_ppe_write_frame_hd_log(ngtcp2_conn *conn, ngtcp2_ppe *ppe, + int *hd_logged, const ngtcp2_pkt_hd *hd, + ngtcp2_frame *fr) { + int rv; + + rv = ngtcp2_ppe_encode_frame(ppe, fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return rv; + } + + if (hd_logged && !*hd_logged) { + *hd_logged = 1; + ngtcp2_log_tx_pkt_hd(&conn->log, hd); + ngtcp2_qlog_pkt_sent_start(&conn->qlog); + } + + ngtcp2_log_tx_fr(&conn->log, hd, fr); + ngtcp2_qlog_write_frame(&conn->qlog, fr); + + return 0; +} + +/* + * conn_ppe_write_frame writes |fr| to |ppe|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer is too small. + */ +static int conn_ppe_write_frame(ngtcp2_conn *conn, ngtcp2_ppe *ppe, + const ngtcp2_pkt_hd *hd, ngtcp2_frame *fr) { + return conn_ppe_write_frame_hd_log(conn, ppe, NULL, hd, fr); +} + +/* + * conn_on_pkt_sent is called when new non-ACK-only packet is sent. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_on_pkt_sent(ngtcp2_conn *conn, ngtcp2_rtb *rtb, + ngtcp2_rtb_entry *ent) { + int rv; + + /* This function implements OnPacketSent, but it handles only + non-ACK-only packet. */ + rv = ngtcp2_rtb_add(rtb, ent, &conn->cstat); + if (rv != 0) { + return rv; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + conn->cstat.last_tx_pkt_ts[rtb->pktns_id] = ent->ts; + } + + ngtcp2_conn_set_loss_detection_timer(conn, ent->ts); + + return 0; +} + +/* + * pktns_select_pkt_numlen selects shortest packet number encoding for + * the next packet number based on the largest acknowledged packet + * number. It returns the number of bytes to encode the packet + * number. + */ +static size_t pktns_select_pkt_numlen(ngtcp2_pktns *pktns) { + int64_t pkt_num = pktns->tx.last_pkt_num + 1; + ngtcp2_rtb *rtb = &pktns->rtb; + int64_t n = pkt_num - rtb->largest_acked_tx_pkt_num; + + if (NGTCP2_MAX_PKT_NUM / 2 < n) { + return 4; + } + + n = n * 2 - 1; + + if (n > 0xffffff) { + return 4; + } + if (n > 0xffff) { + return 3; + } + if (n > 0xff) { + return 2; + } + return 1; +} + +/* + * conn_get_cwnd returns cwnd for the current path. + */ +static uint64_t conn_get_cwnd(ngtcp2_conn *conn) { + return conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) + ? ngtcp2_cc_compute_initcwnd(conn->cstat.max_tx_udp_payload_size) + : conn->cstat.cwnd; +} + +/* + * conn_cwnd_is_zero returns nonzero if the number of bytes the local + * endpoint can sent at this time is zero. + */ +static int conn_cwnd_is_zero(ngtcp2_conn *conn) { + uint64_t bytes_in_flight = conn->cstat.bytes_in_flight; + uint64_t cwnd = conn_get_cwnd(conn); + + if (bytes_in_flight >= cwnd) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV, + "cwnd limited bytes_in_flight=%lu cwnd=%lu", + bytes_in_flight, cwnd); + } + + return bytes_in_flight >= cwnd; +} + +/* + * conn_retry_early_payloadlen returns the estimated wire length of + * the first STREAM frame of 0-RTT packet which should be + * retransmitted due to Retry packet. + */ +static uint64_t conn_retry_early_payloadlen(ngtcp2_conn *conn) { + ngtcp2_frame_chain *frc; + ngtcp2_strm *strm; + uint64_t len; + + if (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) { + return 0; + } + + for (; !ngtcp2_pq_empty(&conn->tx.strmq);) { + strm = ngtcp2_conn_tx_strmq_top(conn); + if (ngtcp2_strm_streamfrq_empty(strm)) { + ngtcp2_conn_tx_strmq_pop(conn); + continue; + } + + frc = ngtcp2_strm_streamfrq_top(strm); + + len = ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt) + + NGTCP2_STREAM_OVERHEAD; + + /* Take the min because in conn_should_pad_pkt we take max in + order to deal with unbreakable DATAGRAM. */ + return ngtcp2_min(len, NGTCP2_MIN_COALESCED_PAYLOADLEN); + } + + return 0; +} + +static void conn_cryptofrq_clear(ngtcp2_conn *conn, ngtcp2_pktns *pktns) { + ngtcp2_frame_chain *frc; + ngtcp2_ksl_it it; + + for (it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + frc = ngtcp2_ksl_it_get(&it); + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + } + ngtcp2_ksl_clear(&pktns->crypto.tx.frq); +} + +/* + * conn_cryptofrq_unacked_offset returns the CRYPTO frame offset by + * taking into account acknowledged offset. If there is no data to + * send, this function returns (uint64_t)-1. + */ +static uint64_t conn_cryptofrq_unacked_offset(ngtcp2_conn *conn, + ngtcp2_pktns *pktns) { + ngtcp2_frame_chain *frc; + ngtcp2_crypto *fr; + ngtcp2_range gap; + ngtcp2_rtb *rtb = &pktns->rtb; + ngtcp2_ksl_it it; + uint64_t datalen; + + (void)conn; + + for (it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + frc = ngtcp2_ksl_it_get(&it); + fr = &frc->fr.crypto; + + gap = ngtcp2_strm_get_unacked_range_after(rtb->crypto, fr->offset); + + datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + + if (gap.begin <= fr->offset) { + return fr->offset; + } + if (gap.begin < fr->offset + datalen) { + return gap.begin; + } + } + + return (uint64_t)-1; +} + +static int conn_cryptofrq_unacked_pop(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_frame_chain **pfrc) { + ngtcp2_frame_chain *frc, *nfrc; + ngtcp2_crypto *fr, *nfr; + uint64_t offset, end_offset; + size_t idx, end_idx; + uint64_t base_offset, end_base_offset; + ngtcp2_range gap; + ngtcp2_rtb *rtb = &pktns->rtb; + ngtcp2_vec *v; + int rv; + ngtcp2_ksl_it it; + + *pfrc = NULL; + + for (it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq); !ngtcp2_ksl_it_end(&it);) { + frc = ngtcp2_ksl_it_get(&it); + fr = &frc->fr.crypto; + + ngtcp2_ksl_remove_hint(&pktns->crypto.tx.frq, &it, &it, &fr->offset); + + idx = 0; + offset = fr->offset; + base_offset = 0; + + gap = ngtcp2_strm_get_unacked_range_after(rtb->crypto, offset); + if (gap.begin < offset) { + gap.begin = offset; + } + + for (; idx < fr->datacnt && offset < gap.begin; ++idx) { + v = &fr->data[idx]; + if (offset + v->len > gap.begin) { + base_offset = gap.begin - offset; + break; + } + + offset += v->len; + } + + if (idx == fr->datacnt) { + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + + assert(gap.begin == offset + base_offset); + + end_idx = idx; + end_offset = offset; + end_base_offset = 0; + + for (; end_idx < fr->datacnt; ++end_idx) { + v = &fr->data[end_idx]; + if (end_offset + v->len > gap.end) { + end_base_offset = gap.end - end_offset; + break; + } + + end_offset += v->len; + } + + if (fr->offset == offset && base_offset == 0 && fr->datacnt == end_idx) { + *pfrc = frc; + return 0; + } + + if (fr->datacnt == end_idx) { + memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx)); + + assert(fr->data[0].len > base_offset); + + fr->offset = offset + base_offset; + fr->datacnt = end_idx - idx; + fr->data[0].base += base_offset; + fr->data[0].len -= (size_t)base_offset; + + *pfrc = frc; + return 0; + } + + rv = ngtcp2_frame_chain_crypto_datacnt_objalloc_new( + &nfrc, fr->datacnt - end_idx, &conn->frc_objalloc, conn->mem); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + + nfr = &nfrc->fr.crypto; + nfr->type = NGTCP2_FRAME_CRYPTO; + memcpy(nfr->data, fr->data + end_idx, + sizeof(nfr->data[0]) * (fr->datacnt - end_idx)); + + assert(nfr->data[0].len > end_base_offset); + + nfr->offset = end_offset + end_base_offset; + nfr->datacnt = fr->datacnt - end_idx; + nfr->data[0].base += end_base_offset; + nfr->data[0].len -= (size_t)end_base_offset; + + rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem); + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + + if (end_base_offset) { + ++end_idx; + } + + memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx)); + + assert(fr->data[0].len > base_offset); + + fr->offset = offset + base_offset; + fr->datacnt = end_idx - idx; + if (end_base_offset) { + assert(fr->data[fr->datacnt - 1].len > end_base_offset); + fr->data[fr->datacnt - 1].len = (size_t)end_base_offset; + } + fr->data[0].base += base_offset; + fr->data[0].len -= (size_t)base_offset; + + *pfrc = frc; + return 0; + } + + return 0; +} +static int conn_cryptofrq_pop(ngtcp2_conn *conn, ngtcp2_frame_chain **pfrc, + ngtcp2_pktns *pktns, size_t left) { + ngtcp2_crypto *fr, *nfr; + ngtcp2_frame_chain *frc, *nfrc; + int rv; + size_t nmerged; + uint64_t datalen; + ngtcp2_vec a[NGTCP2_MAX_CRYPTO_DATACNT]; + ngtcp2_vec b[NGTCP2_MAX_CRYPTO_DATACNT]; + size_t acnt, bcnt; + ngtcp2_ksl_it it; + + rv = conn_cryptofrq_unacked_pop(conn, pktns, &frc); + if (rv != 0) { + return rv; + } + if (frc == NULL) { + *pfrc = NULL; + return 0; + } + + fr = &frc->fr.crypto; + datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + + if (datalen > left) { + ngtcp2_vec_copy(a, fr->data, fr->datacnt); + acnt = fr->datacnt; + + bcnt = 0; + ngtcp2_vec_split(a, &acnt, b, &bcnt, left, NGTCP2_MAX_CRYPTO_DATACNT); + + assert(acnt > 0); + assert(bcnt > 0); + + rv = ngtcp2_frame_chain_crypto_datacnt_objalloc_new( + &nfrc, bcnt, &conn->frc_objalloc, conn->mem); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + + nfr = &nfrc->fr.crypto; + nfr->type = NGTCP2_FRAME_CRYPTO; + nfr->offset = fr->offset + left; + nfr->datacnt = bcnt; + ngtcp2_vec_copy(nfr->data, b, bcnt); + + rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem); + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + + rv = ngtcp2_frame_chain_crypto_datacnt_objalloc_new( + &nfrc, acnt, &conn->frc_objalloc, conn->mem); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + + nfr = &nfrc->fr.crypto; + *nfr = *fr; + nfr->datacnt = acnt; + ngtcp2_vec_copy(nfr->data, a, acnt); + + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + + *pfrc = nfrc; + + return 0; + } + + left -= (size_t)datalen; + + ngtcp2_vec_copy(a, fr->data, fr->datacnt); + acnt = fr->datacnt; + + for (; left && ngtcp2_ksl_len(&pktns->crypto.tx.frq);) { + it = ngtcp2_ksl_begin(&pktns->crypto.tx.frq); + nfrc = ngtcp2_ksl_it_get(&it); + nfr = &nfrc->fr.crypto; + + if (nfr->offset != fr->offset + datalen) { + assert(fr->offset + datalen < nfr->offset); + break; + } + + rv = conn_cryptofrq_unacked_pop(conn, pktns, &nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + if (nfrc == NULL) { + break; + } + + nfr = &nfrc->fr.crypto; + + nmerged = ngtcp2_vec_merge(a, &acnt, nfr->data, &nfr->datacnt, left, + NGTCP2_MAX_CRYPTO_DATACNT); + if (nmerged == 0) { + rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem); + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + break; + } + + datalen += nmerged; + left -= nmerged; + + if (nfr->datacnt == 0) { + ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem); + continue; + } + + nfr->offset += nmerged; + + rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(nfrc, &conn->frc_objalloc, conn->mem); + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + + break; + } + + if (acnt == fr->datacnt) { + assert(acnt > 0); + fr->data[acnt - 1] = a[acnt - 1]; + + *pfrc = frc; + return 0; + } + + assert(acnt > fr->datacnt); + + rv = ngtcp2_frame_chain_crypto_datacnt_objalloc_new( + &nfrc, acnt, &conn->frc_objalloc, conn->mem); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + + nfr = &nfrc->fr.crypto; + *nfr = *fr; + nfr->datacnt = acnt; + ngtcp2_vec_copy(nfr->data, a, acnt); + + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + + *pfrc = nfrc; + + return 0; +} + +/* + * conn_verify_dcid verifies that destination connection ID in |hd| is + * valid for the connection. If it is successfully verified and the + * remote endpoint uses new DCID in the packet, nonzero value is + * assigned to |*pnew_cid_used| if it is not NULL. Otherwise 0 is + * assigned to it. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_INVALID_ARGUMENT + * |dcid| is not known to the local endpoint. + */ +static int conn_verify_dcid(ngtcp2_conn *conn, int *pnew_cid_used, + const ngtcp2_pkt_hd *hd) { + ngtcp2_ksl_it it; + ngtcp2_scid *scid; + int rv; + + it = ngtcp2_ksl_lower_bound(&conn->scid.set, &hd->dcid); + if (ngtcp2_ksl_it_end(&it)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + scid = ngtcp2_ksl_it_get(&it); + if (!ngtcp2_cid_eq(&scid->cid, &hd->dcid)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (!(scid->flags & NGTCP2_SCID_FLAG_USED)) { + scid->flags |= NGTCP2_SCID_FLAG_USED; + + if (scid->pe.index == NGTCP2_PQ_BAD_INDEX) { + rv = ngtcp2_pq_push(&conn->scid.used, &scid->pe); + if (rv != 0) { + return rv; + } + } + + if (pnew_cid_used) { + *pnew_cid_used = 1; + } + } else if (pnew_cid_used) { + *pnew_cid_used = 0; + } + + return 0; +} + +/* + * conn_should_pad_pkt returns nonzero if the packet should be padded. + * |type| is the type of packet. |left| is the space left in packet + * buffer. |write_datalen| is the number of bytes which will be sent + * in the next, coalesced 0-RTT or 1RTT packet. + */ +static int conn_should_pad_pkt(ngtcp2_conn *conn, uint8_t type, size_t left, + uint64_t write_datalen, int ack_eliciting, + int require_padding) { + uint64_t min_payloadlen; + + if (type == NGTCP2_PKT_INITIAL) { + if (conn->server) { + if (!ack_eliciting) { + return 0; + } + + if (conn->hs_pktns->crypto.tx.ckm && + (conn->hs_pktns->rtb.probe_pkt_left || + ngtcp2_ksl_len(&conn->hs_pktns->crypto.tx.frq) || + !ngtcp2_acktr_empty(&conn->hs_pktns->acktr))) { + /* If we have something to send in Handshake packet, then add + PADDING in Handshake packet. */ + min_payloadlen = NGTCP2_MIN_COALESCED_PAYLOADLEN; + } else { + return 1; + } + } else { + if (conn->hs_pktns->crypto.tx.ckm && + (conn->hs_pktns->rtb.probe_pkt_left || + ngtcp2_ksl_len(&conn->hs_pktns->crypto.tx.frq) || + !ngtcp2_acktr_empty(&conn->hs_pktns->acktr))) { + /* If we have something to send in Handshake packet, then add + PADDING in Handshake packet. */ + min_payloadlen = NGTCP2_MIN_COALESCED_PAYLOADLEN; + } else if ((!conn->early.ckm && !conn->pktns.crypto.tx.ckm) || + write_datalen == 0) { + return 1; + } else { + /* If we have something to send in 0RTT or 1RTT packet, then + add PADDING in that packet. Take maximum in case that + write_datalen includes DATAGRAM which cannot be split. */ + min_payloadlen = + ngtcp2_max(write_datalen, NGTCP2_MIN_COALESCED_PAYLOADLEN); + } + } + } else { + assert(type == NGTCP2_PKT_HANDSHAKE); + + if (!require_padding) { + return 0; + } + + if (!conn->pktns.crypto.tx.ckm || write_datalen == 0) { + return 1; + } + + min_payloadlen = ngtcp2_max(write_datalen, NGTCP2_MIN_COALESCED_PAYLOADLEN); + } + + /* TODO the next packet type should be taken into account */ + return left < + /* TODO Assuming that pkt_num is encoded in 1 byte. */ + NGTCP2_MIN_LONG_HEADERLEN + conn->dcid.current.cid.datalen + + conn->oscid.datalen + NGTCP2_PKT_LENGTHLEN - 1 + min_payloadlen + + NGTCP2_MAX_AEAD_OVERHEAD; +} + +static void conn_restart_timer_on_write(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + conn->idle_ts = ts; + conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE; +} + +static void conn_restart_timer_on_read(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + conn->idle_ts = ts; + conn->flags |= NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE; +} + +/* + * conn_keep_alive_enabled returns nonzero if keep-alive is enabled. + */ +static int conn_keep_alive_enabled(ngtcp2_conn *conn) { + return conn->keep_alive.last_ts != UINT64_MAX && conn->keep_alive.timeout; +} + +/* + * conn_keep_alive_expired returns nonzero if keep-alive timer has + * expired. + */ +static int conn_keep_alive_expired(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + return conn_keep_alive_enabled(conn) && + conn->keep_alive.last_ts + conn->keep_alive.timeout <= ts; +} + +/* + * conn_keep_alive_expiry returns the expiry time of keep-alive timer. + */ +static ngtcp2_tstamp conn_keep_alive_expiry(ngtcp2_conn *conn) { + if ((conn->flags & NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED) || + !conn_keep_alive_enabled(conn)) { + return UINT64_MAX; + } + + return conn->keep_alive.last_ts + conn->keep_alive.timeout; +} + +/* + * conn_cancel_expired_keep_alive_timer cancels the expired keep-alive + * timer. + */ +static void conn_cancel_expired_keep_alive_timer(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + if (!(conn->flags & NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED) && + conn_keep_alive_expired(conn, ts)) { + conn->flags |= NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED; + } +} + +/* + * conn_update_keep_alive_last_ts updates the base time point of + * keep-alive timer. + */ +static void conn_update_keep_alive_last_ts(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + conn->keep_alive.last_ts = ts; + conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED; +} + +void ngtcp2_conn_set_keep_alive_timeout(ngtcp2_conn *conn, + ngtcp2_duration timeout) { + conn->keep_alive.timeout = timeout; +} + +/* + * NGTCP2_PKT_PACING_OVERHEAD defines overhead of userspace event + * loop. Packet pacing might require sub milliseconds packet spacing, + * but userspace event loop might not offer such precision. + * Typically, if delay is 0.5 microseconds, the actual delay after + * which we can send packet is well over 1 millisecond when event loop + * is involved (which includes other stuff, like reading packets etc + * in a typical single threaded use case). + */ +#define NGTCP2_PKT_PACING_OVERHEAD NGTCP2_MILLISECONDS + +static void conn_cancel_expired_pkt_tx_timer(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + if (conn->tx.pacing.next_ts == UINT64_MAX) { + return; + } + + if (conn->tx.pacing.next_ts > ts + NGTCP2_PKT_PACING_OVERHEAD) { + return; + } + + conn->tx.pacing.next_ts = UINT64_MAX; +} + +static int conn_pacing_pkt_tx_allowed(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + return conn->tx.pacing.next_ts == UINT64_MAX || + conn->tx.pacing.next_ts <= ts + NGTCP2_PKT_PACING_OVERHEAD; +} + +static uint8_t conn_pkt_flags(ngtcp2_conn *conn) { + if (conn->remote.transport_params && + conn->remote.transport_params->grease_quic_bit && + (conn->flags & NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT)) { + return NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR; + } + + return NGTCP2_PKT_FLAG_NONE; +} + +static uint8_t conn_pkt_flags_long(ngtcp2_conn *conn) { + return NGTCP2_PKT_FLAG_LONG_FORM | conn_pkt_flags(conn); +} + +static uint8_t conn_pkt_flags_short(ngtcp2_conn *conn) { + return (uint8_t)(conn_pkt_flags(conn) | ((conn->pktns.crypto.tx.ckm->flags & + NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE) + ? NGTCP2_PKT_FLAG_KEY_PHASE + : NGTCP2_PKT_FLAG_NONE)); +} + +/* + * conn_write_handshake_pkt writes handshake packet in the buffer + * pointed by |dest| whose length is |destlen|. |type| specifies long + * packet type. It should be either NGTCP2_PKT_INITIAL or + * NGTCP2_PKT_HANDSHAKE_PKT. + * + * |write_datalen| is the minimum length of application data ready to + * send in subsequent 0RTT or 1RTT packet. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize +conn_write_handshake_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint8_t type, uint8_t flags, + uint64_t write_datalen, ngtcp2_tstamp ts) { + int rv; + ngtcp2_ppe ppe; + ngtcp2_pkt_hd hd; + ngtcp2_frame_chain *frq = NULL, **pfrc = &frq; + ngtcp2_frame_chain *nfrc; + ngtcp2_frame *ackfr = NULL, lfr; + ngtcp2_ssize spktlen; + ngtcp2_crypto_cc cc; + ngtcp2_rtb_entry *rtbent; + ngtcp2_pktns *pktns; + size_t left; + uint16_t rtb_entry_flags = NGTCP2_RTB_ENTRY_FLAG_NONE; + int require_padding = (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) != 0; + int pkt_empty = 1; + int padded = 0; + int hd_logged = 0; + uint64_t crypto_offset; + ngtcp2_ssize num_reclaimed; + uint32_t version; + + switch (type) { + case NGTCP2_PKT_INITIAL: + if (!conn->in_pktns) { + return 0; + } + assert(conn->in_pktns->crypto.tx.ckm); + pktns = conn->in_pktns; + version = conn->negotiated_version ? conn->negotiated_version + : conn->client_chosen_version; + if (version == conn->client_chosen_version) { + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + } else { + assert(conn->vneg.version == version); + + cc.ckm = conn->vneg.tx.ckm; + cc.hp_ctx = conn->vneg.tx.hp_ctx; + } + break; + case NGTCP2_PKT_HANDSHAKE: + if (!conn->hs_pktns || !conn->hs_pktns->crypto.tx.ckm) { + return 0; + } + pktns = conn->hs_pktns; + version = conn->negotiated_version; + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + break; + default: + ngtcp2_unreachable(); + } + + cc.aead = pktns->crypto.ctx.aead; + cc.hp = pktns->crypto.ctx.hp; + cc.encrypt = conn->callbacks.encrypt; + cc.hp_mask = conn->callbacks.hp_mask; + + ngtcp2_pkt_hd_init(&hd, conn_pkt_flags_long(conn), type, + &conn->dcid.current.cid, &conn->oscid, + pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns), + version, 0); + + if (!conn->server && type == NGTCP2_PKT_INITIAL && + conn->local.settings.token.len) { + hd.token = conn->local.settings.token; + } + + ngtcp2_ppe_init(&ppe, dest, destlen, &cc); + + rv = ngtcp2_ppe_encode_hd(&ppe, &hd); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return 0; + } + + if (!ngtcp2_ppe_ensure_hp_sample(&ppe)) { + return 0; + } + + rv = conn_create_ack_frame(conn, &ackfr, pktns, type, ts, + /* ack_delay = */ 0, + NGTCP2_DEFAULT_ACK_DELAY_EXPONENT); + if (rv != 0) { + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, conn->mem); + return rv; + } + + if (ackfr) { + rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, ackfr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + ngtcp2_acktr_commit_ack(&pktns->acktr); + ngtcp2_acktr_add_ack(&pktns->acktr, hd.pkt_num, ackfr->ack.largest_ack); + pkt_empty = 0; + } + } + + /* Server requires at least NGTCP2_MAX_UDP_PAYLOAD_SIZE bytes in + order to send ack-eliciting Initial packet. */ + if (!conn->server || type != NGTCP2_PKT_INITIAL || + destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + build_pkt: + for (; ngtcp2_ksl_len(&pktns->crypto.tx.frq);) { + left = ngtcp2_ppe_left(&ppe); + + crypto_offset = conn_cryptofrq_unacked_offset(conn, pktns); + if (crypto_offset == (size_t)-1) { + conn_cryptofrq_clear(conn, pktns); + break; + } + + left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left); + if (left == (size_t)-1) { + break; + } + + rv = conn_cryptofrq_pop(conn, &nfrc, pktns, left); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, + conn->mem); + return rv; + } + + if (nfrc == NULL) { + break; + } + + rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &nfrc->fr); + if (rv != 0) { + ngtcp2_unreachable(); + } + + *pfrc = nfrc; + pfrc = &(*pfrc)->next; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + } + + if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && + pktns->rtb.num_retransmittable && pktns->rtb.probe_pkt_left) { + num_reclaimed = ngtcp2_rtb_reclaim_on_pto(&pktns->rtb, conn, pktns, 1); + if (num_reclaimed < 0) { + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, + conn->mem); + return rv; + } + if (num_reclaimed) { + goto build_pkt; + } + /* We had pktns->rtb.num_retransmittable > 0 but the contents of + those packets have been acknowledged (i.e., retransmission in + another packet). For server, in this case, we don't have to + send any probe packet. Client needs to send probe packets + until it knows that server has completed address validation or + handshake has been confirmed. */ + if (pktns->rtb.num_pto_eliciting == 0 && + (conn->server || + (conn->flags & (NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED | + NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)))) { + pktns->rtb.probe_pkt_left = 0; + ngtcp2_conn_set_loss_detection_timer(conn, ts); + /* TODO If packet is empty, we should return now if cwnd is + zero. */ + } + } + + if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && + pktns->rtb.probe_pkt_left) { + lfr.type = NGTCP2_FRAME_PING; + + rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr); + if (rv != 0) { + assert(rv == NGTCP2_ERR_NOBUF); + } else { + rtb_entry_flags |= + NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | NGTCP2_RTB_ENTRY_FLAG_PROBE; + pkt_empty = 0; + } + } + + if (!pkt_empty) { + if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + /* The intention of smaller limit is get more chance to measure + RTT samples in early phase. */ + if (pktns->tx.num_non_ack_pkt >= 1) { + lfr.type = NGTCP2_FRAME_PING; + + rv = conn_ppe_write_frame_hd_log(conn, &ppe, &hd_logged, &hd, &lfr); + if (rv != 0) { + assert(rv == NGTCP2_ERR_NOBUF); + } else { + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING; + pktns->tx.num_non_ack_pkt = 0; + } + } else { + ++pktns->tx.num_non_ack_pkt; + } + } else { + pktns->tx.num_non_ack_pkt = 0; + } + } + } + + if (pkt_empty) { + return 0; + } + + /* If we cannot write another packet, then we need to add padding to + Initial here. */ + if (conn_should_pad_pkt( + conn, type, ngtcp2_ppe_left(&ppe), write_datalen, + (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) != 0, + require_padding)) { + lfr.type = NGTCP2_FRAME_PADDING; + lfr.padding.len = ngtcp2_ppe_padding(&ppe); + } else { + lfr.type = NGTCP2_FRAME_PADDING; + lfr.padding.len = ngtcp2_ppe_padding_hp_sample(&ppe); + } + + if (lfr.padding.len) { + padded = 1; + ngtcp2_log_tx_fr(&conn->log, &hd, &lfr); + ngtcp2_qlog_write_frame(&conn->qlog, &lfr); + } + + spktlen = ngtcp2_ppe_final(&ppe, NULL); + if (spktlen < 0) { + assert(ngtcp2_err_is_fatal((int)spktlen)); + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, conn->mem); + return spktlen; + } + + ngtcp2_qlog_pkt_sent_end(&conn->qlog, &hd, (size_t)spktlen); + + if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) { + if (pi) { + conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, &hd, ts); + } + + rv = ngtcp2_rtb_entry_objalloc_new(&rtbent, &hd, frq, ts, (size_t)spktlen, + rtb_entry_flags, + &conn->rtb_entry_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_list_objalloc_del(frq, &conn->frc_objalloc, conn->mem); + return rv; + } + + rv = conn_on_pkt_sent(conn, &pktns->rtb, rtbent); + if (rv != 0) { + ngtcp2_rtb_entry_objalloc_del(rtbent, &conn->rtb_entry_objalloc, + &conn->frc_objalloc, conn->mem); + return rv; + } + + if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && + (conn->flags & NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE)) { + conn_restart_timer_on_write(conn, ts); + } + } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) { + conn_handle_tx_ecn(conn, pi, NULL, pktns, &hd, ts); + } + + if (pktns->rtb.probe_pkt_left && + (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + --pktns->rtb.probe_pkt_left; + } + + conn_update_keep_alive_last_ts(conn, ts); + + conn->dcid.current.bytes_sent += (uint64_t)spktlen; + + conn->tx.pacing.pktlen += (size_t)spktlen; + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + ++pktns->tx.last_pkt_num; + + return spktlen; +} + +/* + * conn_write_ack_pkt writes QUIC packet for type |type| which only + * includes ACK frame in the buffer pointed by |dest| whose length is + * |destlen|. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static ngtcp2_ssize conn_write_ack_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint8_t type, ngtcp2_tstamp ts) { + int rv; + ngtcp2_frame *ackfr; + ngtcp2_pktns *pktns; + ngtcp2_duration ack_delay; + uint64_t ack_delay_exponent; + ngtcp2_ssize spktlen; + + assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); + + switch (type) { + case NGTCP2_PKT_INITIAL: + assert(conn->server); + pktns = conn->in_pktns; + ack_delay = 0; + ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + break; + case NGTCP2_PKT_HANDSHAKE: + pktns = conn->hs_pktns; + ack_delay = 0; + ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + break; + case NGTCP2_PKT_1RTT: + pktns = &conn->pktns; + ack_delay = conn_compute_ack_delay(conn); + ack_delay_exponent = conn->local.transport_params.ack_delay_exponent; + break; + default: + ngtcp2_unreachable(); + } + + if (!pktns->crypto.tx.ckm) { + return 0; + } + + ackfr = NULL; + rv = conn_create_ack_frame(conn, &ackfr, pktns, type, ts, ack_delay, + ack_delay_exponent); + if (rv != 0) { + return rv; + } + + if (!ackfr) { + return 0; + } + + spktlen = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, type, NGTCP2_WRITE_PKT_FLAG_NONE, + &conn->dcid.current.cid, ackfr, NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + + if (spktlen <= 0) { + return spktlen; + } + + conn->dcid.current.bytes_sent += (uint64_t)spktlen; + + return spktlen; +} + +static void conn_discard_pktns(ngtcp2_conn *conn, ngtcp2_pktns **ppktns, + ngtcp2_tstamp ts) { + ngtcp2_pktns *pktns = *ppktns; + uint64_t bytes_in_flight; + + bytes_in_flight = pktns->rtb.cc_bytes_in_flight; + + assert(conn->cstat.bytes_in_flight >= bytes_in_flight); + + conn->cstat.bytes_in_flight -= bytes_in_flight; + conn->cstat.pto_count = 0; + conn->cstat.last_tx_pkt_ts[pktns->rtb.pktns_id] = UINT64_MAX; + conn->cstat.loss_time[pktns->rtb.pktns_id] = UINT64_MAX; + + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.rx.ckm->aead_ctx); + conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.rx.hp_ctx); + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx); + conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.tx.hp_ctx); + + pktns_del(pktns, conn->mem); + *ppktns = NULL; + + ngtcp2_conn_set_loss_detection_timer(conn, ts); +} + +/* + * conn_discard_initial_state discards state for Initial packet number + * space. + */ +static void conn_discard_initial_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + if (!conn->in_pktns) { + return; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "discarding Initial packet number space"); + + conn_discard_pktns(conn, &conn->in_pktns, ts); + + conn_vneg_crypto_free(conn); + + memset(&conn->vneg.rx, 0, sizeof(conn->vneg.rx)); + memset(&conn->vneg.tx, 0, sizeof(conn->vneg.tx)); +} + +/* + * conn_discard_handshake_state discards state for Handshake packet + * number space. + */ +static void conn_discard_handshake_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + if (!conn->hs_pktns) { + return; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "discarding Handshake packet number space"); + + conn_discard_pktns(conn, &conn->hs_pktns, ts); +} + +/* + * conn_discard_early_key discards early key. + */ +static void conn_discard_early_key(ngtcp2_conn *conn) { + assert(conn->early.ckm); + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "discarding early key"); + + conn_call_delete_crypto_aead_ctx(conn, &conn->early.ckm->aead_ctx); + conn_call_delete_crypto_cipher_ctx(conn, &conn->early.hp_ctx); + memset(&conn->early.hp_ctx, 0, sizeof(conn->early.hp_ctx)); + + ngtcp2_crypto_km_del(conn->early.ckm, conn->mem); + conn->early.ckm = NULL; +} + +/* + * conn_write_handshake_ack_pkts writes packets which contain ACK + * frame only. This function writes at most 2 packets for each + * Initial and Handshake packet. + */ +static ngtcp2_ssize conn_write_handshake_ack_pkts(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_tstamp ts) { + ngtcp2_ssize res = 0, nwrite = 0; + + /* In the most cases, client sends ACK in conn_write_handshake_pkt. + This function is only called when it is CWND limited or pacing + limited. It is not required for client to send ACK for server + Initial. This is because once it gets server Initial, it gets + Handshake tx key and discards Initial key. The only good reason + to send ACK is give server RTT measurement early. */ + if (conn->server && conn->in_pktns) { + nwrite = + conn_write_ack_pkt(conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, ts); + if (nwrite < 0) { + assert(nwrite != NGTCP2_ERR_NOBUF); + return nwrite; + } + + res += nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } + + if (conn->hs_pktns->crypto.tx.ckm) { + nwrite = + conn_write_ack_pkt(conn, pi, dest, destlen, NGTCP2_PKT_HANDSHAKE, ts); + if (nwrite < 0) { + assert(nwrite != NGTCP2_ERR_NOBUF); + return nwrite; + } + + res += nwrite; + + if (!conn->server && nwrite) { + conn_discard_initial_state(conn, ts); + } + } + + return res; +} + +/* + * conn_write_client_initial writes Initial packet in the buffer + * pointed by |dest| whose length is |destlen|. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize conn_write_client_initial(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint64_t early_datalen, + ngtcp2_tstamp ts) { + int rv; + + rv = conn_call_client_initial(conn); + if (rv != 0) { + return rv; + } + + return conn_write_handshake_pkt(conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, + NGTCP2_WRITE_PKT_FLAG_NONE, early_datalen, + ts); +} + +/* + * dcid_tx_left returns the maximum number of bytes that server is + * allowed to send to an unvalidated path associated to |dcid|. + */ +static uint64_t dcid_tx_left(ngtcp2_dcid *dcid) { + if (dcid->flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) { + return SIZE_MAX; + } + /* From QUIC spec: Prior to validating the client address, servers + MUST NOT send more than three times as many bytes as the number + of bytes they have received. */ + assert(dcid->bytes_recv * 3 >= dcid->bytes_sent); + + return dcid->bytes_recv * 3 - dcid->bytes_sent; +} + +/* + * conn_server_tx_left returns the maximum number of bytes that server + * is allowed to send to an unvalidated path. + */ +static uint64_t conn_server_tx_left(ngtcp2_conn *conn, ngtcp2_dcid *dcid) { + assert(conn->server); + + /* If pv->dcid has the current path, use conn->dcid.current. This + is because conn->dcid.current gets update for bytes_recv and + bytes_sent. */ + if (ngtcp2_path_eq(&dcid->ps.path, &conn->dcid.current.ps.path)) { + return dcid_tx_left(&conn->dcid.current); + } + + return dcid_tx_left(dcid); +} + +/* + * conn_write_handshake_pkts writes Initial and Handshake packets in + * the buffer pointed by |dest| whose length is |destlen|. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize conn_write_handshake_pkts(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint64_t write_datalen, + ngtcp2_tstamp ts) { + ngtcp2_ssize nwrite; + ngtcp2_ssize res = 0; + ngtcp2_rtb_entry *rtbent; + uint8_t wflags = NGTCP2_WRITE_PKT_FLAG_NONE; + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_ksl_it it; + + /* As a client, we would like to discard Initial packet number space + when sending the first Handshake packet. When sending Handshake + packet, it should be one of 1) sending ACK, 2) sending PTO probe + packet, or 3) sending CRYPTO. If we have pending acknowledgement + for Initial, then do not discard Initial packet number space. + Otherwise, if either 1) or 2) is satisfied, discard Initial + packet number space. When sending Handshake CRYPTO, it indicates + that client has received Handshake CRYPTO from server. Initial + packet number space is discarded because 1) is met. If there is + pending Initial ACK, Initial packet number space is discarded + after writing the first Handshake packet. + */ + if (!conn->server && conn->hs_pktns->crypto.tx.ckm && conn->in_pktns && + !ngtcp2_acktr_require_active_ack(&conn->in_pktns->acktr, + /* max_ack_delay = */ 0, ts) && + (ngtcp2_acktr_require_active_ack(&conn->hs_pktns->acktr, + /* max_ack_delay = */ 0, ts) || + conn->hs_pktns->rtb.probe_pkt_left)) { + /* Discard Initial state here so that Handshake packet is not + padded. */ + conn_discard_initial_state(conn, ts); + } else if (conn->in_pktns) { + nwrite = + conn_write_handshake_pkt(conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, + NGTCP2_WRITE_PKT_FLAG_NONE, write_datalen, ts); + if (nwrite < 0) { + assert(nwrite != NGTCP2_ERR_NOBUF); + return nwrite; + } + + if (nwrite == 0) { + if (conn->server && (conn->in_pktns->rtb.probe_pkt_left || + ngtcp2_ksl_len(&conn->in_pktns->crypto.tx.frq))) { + if (cstat->loss_detection_timer != UINT64_MAX && + conn_server_tx_left(conn, &conn->dcid.current) < + NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_RCV, + "loss detection timer canceled due to amplification limit"); + cstat->loss_detection_timer = UINT64_MAX; + } + + return 0; + } + } else { + res += nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + + if (destlen) { + /* We might have already added padding to Initial, but in that + case, we should have destlen == 0 and no Handshake packet + will be written. */ + if (conn->server) { + it = ngtcp2_rtb_head(&conn->in_pktns->rtb); + if (!ngtcp2_ksl_it_end(&it)) { + rtbent = ngtcp2_ksl_it_get(&it); + if (rtbent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + } + } else { + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + } + } + } + + nwrite = conn_write_handshake_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_HANDSHAKE, wflags, write_datalen, ts); + if (nwrite < 0) { + assert(nwrite != NGTCP2_ERR_NOBUF); + return nwrite; + } + + res += nwrite; + + if (!conn->server && conn->hs_pktns->crypto.tx.ckm && nwrite) { + /* We don't need to send further Initial packet if we have + Handshake key and sent something with it. So discard initial + state here. */ + conn_discard_initial_state(conn, ts); + } + + return res; +} + +/* + * conn_initial_stream_rx_offset returns the initial maximum offset of + * data for a stream denoted by |stream_id|. + */ +static uint64_t conn_initial_stream_rx_offset(ngtcp2_conn *conn, + int64_t stream_id) { + int local_stream = conn_local_stream(conn, stream_id); + + if (bidi_stream(stream_id)) { + if (local_stream) { + return conn->local.transport_params.initial_max_stream_data_bidi_local; + } + return conn->local.transport_params.initial_max_stream_data_bidi_remote; + } + + if (local_stream) { + return 0; + } + return conn->local.transport_params.initial_max_stream_data_uni; +} + +/* + * conn_should_send_max_stream_data returns nonzero if MAX_STREAM_DATA + * frame should be send for |strm|. + */ +static int conn_should_send_max_stream_data(ngtcp2_conn *conn, + ngtcp2_strm *strm) { + uint64_t inc = strm->rx.unsent_max_offset - strm->rx.max_offset; + (void)conn; + + return strm->rx.window < 2 * inc; +} + +/* + * conn_should_send_max_data returns nonzero if MAX_DATA frame should + * be sent. + */ +static int conn_should_send_max_data(ngtcp2_conn *conn) { + uint64_t inc = conn->rx.unsent_max_offset - conn->rx.max_offset; + + return conn->rx.window < 2 * inc; +} + +/* + * conn_required_num_new_connection_id returns the number of + * additional connection ID the local endpoint has to provide to the + * remote endpoint. + */ +static size_t conn_required_num_new_connection_id(ngtcp2_conn *conn) { + uint64_t n; + size_t len = ngtcp2_ksl_len(&conn->scid.set); + + if (len >= NGTCP2_MAX_SCID_POOL_SIZE) { + return 0; + } + + assert(conn->remote.transport_params); + assert(conn->remote.transport_params->active_connection_id_limit); + + /* len includes retired CID. We don't provide extra CID if doing so + exceeds NGTCP2_MAX_SCID_POOL_SIZE. */ + + n = conn->remote.transport_params->active_connection_id_limit + + conn->scid.num_retired; + + return (size_t)ngtcp2_min(NGTCP2_MAX_SCID_POOL_SIZE, n) - len; +} + +/* + * conn_enqueue_new_connection_id generates additional connection IDs + * and prepares to send them to the remote endpoint. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_enqueue_new_connection_id(ngtcp2_conn *conn) { + size_t i, need = conn_required_num_new_connection_id(conn); + size_t cidlen = conn->oscid.datalen; + ngtcp2_cid cid; + uint64_t seq; + int rv; + uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN]; + ngtcp2_frame_chain *nfrc; + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_scid *scid; + ngtcp2_ksl_it it; + + for (i = 0; i < need; ++i) { + rv = conn_call_get_new_connection_id(conn, &cid, token, cidlen); + if (rv != 0) { + return rv; + } + + if (cid.datalen != cidlen) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + /* Assert uniqueness */ + it = ngtcp2_ksl_lower_bound(&conn->scid.set, &cid); + if (!ngtcp2_ksl_it_end(&it) && + ngtcp2_cid_eq(ngtcp2_ksl_it_key(&it), &cid)) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + seq = ++conn->scid.last_seq; + + scid = ngtcp2_mem_malloc(conn->mem, sizeof(*scid)); + if (scid == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_scid_init(scid, seq, &cid); + + rv = ngtcp2_ksl_insert(&conn->scid.set, NULL, &scid->cid, scid); + if (rv != 0) { + ngtcp2_mem_free(conn->mem, scid); + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + nfrc->fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + nfrc->fr.new_connection_id.seq = seq; + nfrc->fr.new_connection_id.retire_prior_to = 0; + nfrc->fr.new_connection_id.cid = cid; + memcpy(nfrc->fr.new_connection_id.stateless_reset_token, token, + sizeof(token)); + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + } + + return 0; +} + +/* + * conn_remove_retired_connection_id removes the already retired + * connection ID. It waits PTO before actually removing a connection + * ID after it receives RETIRE_CONNECTION_ID from peer to catch + * reordered packets. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_remove_retired_connection_id(ngtcp2_conn *conn, + ngtcp2_duration pto, + ngtcp2_tstamp ts) { + ngtcp2_duration timeout = pto; + ngtcp2_scid *scid; + ngtcp2_dcid *dcid; + int rv; + + for (; !ngtcp2_pq_empty(&conn->scid.used);) { + scid = ngtcp2_struct_of(ngtcp2_pq_top(&conn->scid.used), ngtcp2_scid, pe); + + if (scid->retired_ts == UINT64_MAX || scid->retired_ts + timeout >= ts) { + break; + } + + assert(scid->flags & NGTCP2_SCID_FLAG_RETIRED); + + rv = conn_call_remove_connection_id(conn, &scid->cid); + if (rv != 0) { + return rv; + } + + ngtcp2_ksl_remove(&conn->scid.set, NULL, &scid->cid); + ngtcp2_pq_pop(&conn->scid.used); + ngtcp2_mem_free(conn->mem, scid); + + assert(conn->scid.num_retired); + --conn->scid.num_retired; + } + + for (; ngtcp2_ringbuf_len(&conn->dcid.retired.rb);) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, 0); + if (dcid->retired_ts + timeout >= ts) { + break; + } + + rv = conn_call_deactivate_dcid(conn, dcid); + if (rv != 0) { + return rv; + } + + ngtcp2_ringbuf_pop_front(&conn->dcid.retired.rb); + } + + return 0; +} + +/* + * conn_min_short_pktlen returns the minimum length of Short packet + * this endpoint sends. + */ +static size_t conn_min_short_pktlen(ngtcp2_conn *conn) { + return conn->dcid.current.cid.datalen + NGTCP2_MIN_PKT_EXPANDLEN; +} + +/* + * conn_handle_unconfirmed_key_update_from_remote deals with key + * update which has not been confirmed yet and initiated by the remote + * endpoint. + * + * If key update was initiated by the remote endpoint, acknowledging a + * packet encrypted with the new key completes key update procedure. + */ +static void conn_handle_unconfirmed_key_update_from_remote(ngtcp2_conn *conn, + int64_t largest_ack, + ngtcp2_tstamp ts) { + if (!(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) || + (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR) || + largest_ack < conn->pktns.crypto.rx.ckm->pkt_num) { + return; + } + + conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED; + conn->crypto.key_update.confirmed_ts = ts; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CRY, "key update confirmed"); +} + +/* + * conn_write_pkt writes a protected packet in the buffer pointed by + * |dest| whose length if |destlen|. |type| specifies the type of + * packet. It can be NGTCP2_PKT_1RTT or NGTCP2_PKT_0RTT. + * + * This function can send new stream data. In order to send stream + * data, specify the underlying stream and parameters to + * |vmsg|->stream. If |vmsg|->stream.fin is set to nonzero, it + * signals that the given data is the final portion of the stream. + * |vmsg|->stream.data vector of length |vmsg|->stream.datacnt + * specifies stream data to send. The number of bytes sent to the + * stream is assigned to *|vmsg|->stream.pdatalen. If 0 length STREAM + * data is sent, 0 is assigned to it. The caller should initialize + * *|vmsg|->stream.pdatalen to -1. + * + * If |require_padding| is nonzero, padding bytes are added to occupy + * the remaining packet payload. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_STREAM_DATA_BLOCKED + * Stream data could not be written because of flow control. + */ +static ngtcp2_ssize conn_write_pkt(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_vmsg *vmsg, uint8_t type, + uint8_t flags, ngtcp2_tstamp ts) { + int rv = 0; + ngtcp2_crypto_cc *cc = &conn->pkt.cc; + ngtcp2_ppe *ppe = &conn->pkt.ppe; + ngtcp2_pkt_hd *hd = &conn->pkt.hd; + ngtcp2_frame *ackfr = NULL, lfr; + ngtcp2_ssize nwrite; + ngtcp2_frame_chain **pfrc, *nfrc, *frc; + ngtcp2_rtb_entry *ent; + ngtcp2_strm *strm; + int pkt_empty = 1; + uint64_t ndatalen = 0; + int send_stream = 0; + int stream_blocked = 0; + int send_datagram = 0; + ngtcp2_pktns *pktns = &conn->pktns; + size_t left; + uint64_t datalen = 0; + ngtcp2_vec data[NGTCP2_MAX_STREAM_DATACNT]; + size_t datacnt; + uint16_t rtb_entry_flags = NGTCP2_RTB_ENTRY_FLAG_NONE; + int hd_logged = 0; + ngtcp2_path_challenge_entry *pcent; + uint8_t hd_flags = NGTCP2_PKT_FLAG_NONE; + int require_padding = (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) != 0; + int write_more = (flags & NGTCP2_WRITE_PKT_FLAG_MORE) != 0; + int ppe_pending = (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) != 0; + size_t min_pktlen = conn_min_short_pktlen(conn); + int padded = 0; + ngtcp2_cc_pkt cc_pkt; + uint64_t crypto_offset; + uint64_t stream_offset; + ngtcp2_ssize num_reclaimed; + int fin; + uint64_t target_max_data; + ngtcp2_conn_stat *cstat = &conn->cstat; + uint64_t delta; + const ngtcp2_cid *scid = NULL; + int keep_alive_expired = 0; + uint32_t version = 0; + + /* Return 0 if destlen is less than minimum packet length which can + trigger Stateless Reset */ + if (destlen < min_pktlen) { + return 0; + } + + if (vmsg) { + switch (vmsg->type) { + case NGTCP2_VMSG_TYPE_STREAM: + datalen = ngtcp2_vec_len(vmsg->stream.data, vmsg->stream.datacnt); + ndatalen = conn_enforce_flow_control(conn, vmsg->stream.strm, datalen); + /* 0 length STREAM frame is allowed */ + if (ndatalen || datalen == 0) { + send_stream = 1; + } else { + stream_blocked = 1; + } + break; + case NGTCP2_VMSG_TYPE_DATAGRAM: + datalen = ngtcp2_vec_len(vmsg->datagram.data, vmsg->datagram.datacnt); + send_datagram = 1; + break; + default: + break; + } + } + + if (!ppe_pending) { + switch (type) { + case NGTCP2_PKT_1RTT: + hd_flags = conn_pkt_flags_short(conn); + scid = NULL; + cc->aead = pktns->crypto.ctx.aead; + cc->hp = pktns->crypto.ctx.hp; + cc->ckm = pktns->crypto.tx.ckm; + cc->hp_ctx = pktns->crypto.tx.hp_ctx; + + assert(conn->negotiated_version); + + version = conn->negotiated_version; + + /* transport parameter is only valid after handshake completion + which means we don't know how many connection ID that remote + peer can accept before handshake completion. */ + if (conn->oscid.datalen && conn_is_handshake_completed(conn)) { + rv = conn_enqueue_new_connection_id(conn); + if (rv != 0) { + return rv; + } + } + + break; + case NGTCP2_PKT_0RTT: + assert(!conn->server); + if (!conn->early.ckm) { + return 0; + } + hd_flags = conn_pkt_flags_long(conn); + scid = &conn->oscid; + cc->aead = conn->early.ctx.aead; + cc->hp = conn->early.ctx.hp; + cc->ckm = conn->early.ckm; + cc->hp_ctx = conn->early.hp_ctx; + version = conn->client_chosen_version; + break; + default: + /* Unreachable */ + ngtcp2_unreachable(); + } + + cc->encrypt = conn->callbacks.encrypt; + cc->hp_mask = conn->callbacks.hp_mask; + + if (conn_should_send_max_data(conn)) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + if (conn->local.settings.max_window && + conn->tx.last_max_data_ts != UINT64_MAX && + ts - conn->tx.last_max_data_ts < + NGTCP2_FLOW_WINDOW_RTT_FACTOR * cstat->smoothed_rtt && + conn->local.settings.max_window > conn->rx.window) { + target_max_data = NGTCP2_FLOW_WINDOW_SCALING_FACTOR * conn->rx.window; + if (target_max_data > conn->local.settings.max_window) { + target_max_data = conn->local.settings.max_window; + } + + delta = target_max_data - conn->rx.window; + if (conn->rx.unsent_max_offset + delta > NGTCP2_MAX_VARINT) { + delta = NGTCP2_MAX_VARINT - conn->rx.unsent_max_offset; + } + + conn->rx.window = target_max_data; + } else { + delta = 0; + } + + conn->tx.last_max_data_ts = ts; + + nfrc->fr.type = NGTCP2_FRAME_MAX_DATA; + nfrc->fr.max_data.max_data = conn->rx.unsent_max_offset + delta; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + + conn->rx.max_offset = conn->rx.unsent_max_offset = + nfrc->fr.max_data.max_data; + } + + ngtcp2_pkt_hd_init(hd, hd_flags, type, &conn->dcid.current.cid, scid, + pktns->tx.last_pkt_num + 1, + pktns_select_pkt_numlen(pktns), version, 0); + + ngtcp2_ppe_init(ppe, dest, destlen, cc); + + rv = ngtcp2_ppe_encode_hd(ppe, hd); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return 0; + } + + if (!ngtcp2_ppe_ensure_hp_sample(ppe)) { + return 0; + } + + if (ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb)) { + pcent = ngtcp2_ringbuf_get(&conn->rx.path_challenge.rb, 0); + + /* PATH_RESPONSE is bound to the path that the corresponding + PATH_CHALLENGE is received. */ + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, &pcent->ps.path)) { + lfr.type = NGTCP2_FRAME_PATH_RESPONSE; + memcpy(lfr.path_response.data, pcent->data, + sizeof(lfr.path_response.data)); + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb); + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING; + require_padding = + !conn->server || destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE; + /* We don't retransmit PATH_RESPONSE. */ + } + } + } + + rv = conn_create_ack_frame(conn, &ackfr, pktns, type, ts, + conn_compute_ack_delay(conn), + conn->local.transport_params.ack_delay_exponent); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + if (ackfr) { + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, ackfr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + ngtcp2_acktr_commit_ack(&pktns->acktr); + ngtcp2_acktr_add_ack(&pktns->acktr, hd->pkt_num, + ackfr->ack.largest_ack); + if (type == NGTCP2_PKT_1RTT) { + conn_handle_unconfirmed_key_update_from_remote( + conn, ackfr->ack.largest_ack, ts); + } + pkt_empty = 0; + } + } + + build_pkt: + for (pfrc = &pktns->tx.frq; *pfrc;) { + if ((*pfrc)->binder && + ((*pfrc)->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK)) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + + switch ((*pfrc)->fr.type) { + case NGTCP2_FRAME_STOP_SENDING: + strm = + ngtcp2_conn_find_stream(conn, (*pfrc)->fr.stop_sending.stream_id); + if (strm == NULL || (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD)) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + + if (!(strm->flags & NGTCP2_STRM_FLAG_STREAM_STOP_SENDING_CALLED)) { + strm->flags |= NGTCP2_STRM_FLAG_STREAM_STOP_SENDING_CALLED; + + rv = conn_call_stream_stop_sending( + conn, (*pfrc)->fr.stop_sending.stream_id, + (*pfrc)->fr.stop_sending.app_error_code, strm->stream_user_data); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + } + + break; + case NGTCP2_FRAME_STREAM: + ngtcp2_unreachable(); + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + if ((*pfrc)->fr.max_streams.max_streams < + conn->remote.bidi.max_streams) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_MAX_STREAMS_UNI: + if ((*pfrc)->fr.max_streams.max_streams < + conn->remote.uni.max_streams) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_MAX_STREAM_DATA: + strm = ngtcp2_conn_find_stream(conn, + (*pfrc)->fr.max_stream_data.stream_id); + if (strm == NULL || (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) || + (*pfrc)->fr.max_stream_data.max_stream_data < strm->rx.max_offset) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_MAX_DATA: + if ((*pfrc)->fr.max_data.max_data < conn->rx.max_offset) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + continue; + } + break; + case NGTCP2_FRAME_CRYPTO: + ngtcp2_unreachable(); + } + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + break; + } + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + + if (rv != NGTCP2_ERR_NOBUF) { + for (; ngtcp2_ksl_len(&pktns->crypto.tx.frq);) { + left = ngtcp2_ppe_left(ppe); + + crypto_offset = conn_cryptofrq_unacked_offset(conn, pktns); + if (crypto_offset == (size_t)-1) { + conn_cryptofrq_clear(conn, pktns); + break; + } + + left = ngtcp2_pkt_crypto_max_datalen(crypto_offset, left, left); + + if (left == (size_t)-1) { + break; + } + + rv = conn_cryptofrq_pop(conn, &nfrc, pktns, left); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + if (nfrc == NULL) { + break; + } + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + ngtcp2_unreachable(); + } + + *pfrc = nfrc; + pfrc = &(*pfrc)->next; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + } + } + + /* Write MAX_STREAM_ID after RESET_STREAM so that we can extend stream + ID space in one packet. */ + if (rv != NGTCP2_ERR_NOBUF && *pfrc == NULL && + conn->remote.bidi.unsent_max_streams > conn->remote.bidi.max_streams) { + rv = conn_call_extend_max_remote_streams_bidi( + conn, conn->remote.bidi.unsent_max_streams); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + nfrc->fr.type = NGTCP2_FRAME_MAX_STREAMS_BIDI; + nfrc->fr.max_streams.max_streams = conn->remote.bidi.unsent_max_streams; + *pfrc = nfrc; + + conn->remote.bidi.max_streams = conn->remote.bidi.unsent_max_streams; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &(*pfrc)->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + } + + if (rv != NGTCP2_ERR_NOBUF && *pfrc == NULL) { + if (conn->remote.uni.unsent_max_streams > conn->remote.uni.max_streams) { + rv = conn_call_extend_max_remote_streams_uni( + conn, conn->remote.uni.unsent_max_streams); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + nfrc->fr.type = NGTCP2_FRAME_MAX_STREAMS_UNI; + nfrc->fr.max_streams.max_streams = conn->remote.uni.unsent_max_streams; + *pfrc = nfrc; + + conn->remote.uni.max_streams = conn->remote.uni.unsent_max_streams; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, + &(*pfrc)->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + } else { + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + } + } + } + + if (rv != NGTCP2_ERR_NOBUF) { + for (; !ngtcp2_pq_empty(&conn->tx.strmq);) { + strm = ngtcp2_conn_tx_strmq_top(conn); + + if (!(strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + conn_should_send_max_stream_data(conn, strm)) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + if (conn->local.settings.max_stream_window && + strm->tx.last_max_stream_data_ts != UINT64_MAX && + ts - strm->tx.last_max_stream_data_ts < + NGTCP2_FLOW_WINDOW_RTT_FACTOR * cstat->smoothed_rtt && + conn->local.settings.max_stream_window > strm->rx.window) { + target_max_data = + NGTCP2_FLOW_WINDOW_SCALING_FACTOR * strm->rx.window; + if (target_max_data > conn->local.settings.max_stream_window) { + target_max_data = conn->local.settings.max_stream_window; + } + + delta = target_max_data - strm->rx.window; + if (strm->rx.unsent_max_offset + delta > NGTCP2_MAX_VARINT) { + delta = NGTCP2_MAX_VARINT - strm->rx.unsent_max_offset; + } + + strm->rx.window = target_max_data; + } else { + delta = 0; + } + + strm->tx.last_max_stream_data_ts = ts; + + nfrc->fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; + nfrc->fr.max_stream_data.stream_id = strm->stream_id; + nfrc->fr.max_stream_data.max_stream_data = + strm->rx.unsent_max_offset + delta; + ngtcp2_list_insert(nfrc, pfrc); + + rv = + conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + break; + } + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + pfrc = &(*pfrc)->next; + strm->rx.max_offset = strm->rx.unsent_max_offset = + nfrc->fr.max_stream_data.max_stream_data; + } + + if (ngtcp2_strm_streamfrq_empty(strm)) { + ngtcp2_conn_tx_strmq_pop(conn); + continue; + } + + stream_offset = ngtcp2_strm_streamfrq_unacked_offset(strm); + if (stream_offset == (uint64_t)-1) { + ngtcp2_strm_streamfrq_clear(strm); + ngtcp2_conn_tx_strmq_pop(conn); + assert(conn->tx.strmq_nretrans); + --conn->tx.strmq_nretrans; + continue; + } + + left = ngtcp2_ppe_left(ppe); + + left = ngtcp2_pkt_stream_max_datalen(strm->stream_id, stream_offset, + left, left); + + if (left == (size_t)-1) { + break; + } + + rv = ngtcp2_strm_streamfrq_pop(strm, &nfrc, left); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + if (nfrc == NULL) { + /* TODO Why? */ + break; + } + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + ngtcp2_unreachable(); + } + + *pfrc = nfrc; + pfrc = &(*pfrc)->next; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + + if (ngtcp2_strm_streamfrq_empty(strm)) { + ngtcp2_conn_tx_strmq_pop(conn); + assert(conn->tx.strmq_nretrans); + --conn->tx.strmq_nretrans; + continue; + } + + ngtcp2_conn_tx_strmq_pop(conn); + ++strm->cycle; + rv = ngtcp2_conn_tx_strmq_push(conn, strm); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + } + } + + if (rv != NGTCP2_ERR_NOBUF && !send_stream && !send_datagram && + !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && + pktns->rtb.num_retransmittable && pktns->tx.frq == NULL && + pktns->rtb.probe_pkt_left) { + num_reclaimed = ngtcp2_rtb_reclaim_on_pto(&pktns->rtb, conn, pktns, 1); + if (num_reclaimed < 0) { + return rv; + } + if (num_reclaimed) { + goto build_pkt; + } + + /* We had pktns->rtb.num_retransmittable > 0 but we were unable + to reclaim any frame. In this case, we do not have to send + any probe packet. */ + if (pktns->rtb.num_pto_eliciting == 0) { + pktns->rtb.probe_pkt_left = 0; + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + if (pkt_empty && conn_cwnd_is_zero(conn) && !require_padding) { + return 0; + } + } + } + } else { + pfrc = conn->pkt.pfrc; + rtb_entry_flags |= conn->pkt.rtb_entry_flags; + pkt_empty = conn->pkt.pkt_empty; + hd_logged = conn->pkt.hd_logged; + } + + left = ngtcp2_ppe_left(ppe); + + if (rv != NGTCP2_ERR_NOBUF && send_stream && *pfrc == NULL && + (ndatalen = ngtcp2_pkt_stream_max_datalen( + vmsg->stream.strm->stream_id, vmsg->stream.strm->tx.offset, ndatalen, + left)) != (size_t)-1 && + (ndatalen || datalen == 0)) { + datacnt = ngtcp2_vec_copy_at_most(data, NGTCP2_MAX_STREAM_DATACNT, + vmsg->stream.data, vmsg->stream.datacnt, + (size_t)ndatalen); + ndatalen = ngtcp2_vec_len(data, datacnt); + + assert((datacnt == 0 && datalen == 0) || (datacnt && datalen)); + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, datacnt, &conn->frc_objalloc, conn->mem); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + nfrc->fr.stream.type = NGTCP2_FRAME_STREAM; + nfrc->fr.stream.flags = 0; + nfrc->fr.stream.stream_id = vmsg->stream.strm->stream_id; + nfrc->fr.stream.offset = vmsg->stream.strm->tx.offset; + nfrc->fr.stream.datacnt = datacnt; + ngtcp2_vec_copy(nfrc->fr.stream.data, data, datacnt); + + fin = (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_FIN) && + ndatalen == datalen; + nfrc->fr.stream.fin = (uint8_t)fin; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + if (rv != 0) { + ngtcp2_unreachable(); + } + + *pfrc = nfrc; + pfrc = &(*pfrc)->next; + + pkt_empty = 0; + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE; + + vmsg->stream.strm->tx.offset += ndatalen; + conn->tx.offset += ndatalen; + + if (fin) { + ngtcp2_strm_shutdown(vmsg->stream.strm, NGTCP2_STRM_FLAG_SHUT_WR); + } + + if (vmsg->stream.pdatalen) { + *vmsg->stream.pdatalen = (ngtcp2_ssize)ndatalen; + } + } else { + send_stream = 0; + } + + if (rv != NGTCP2_ERR_NOBUF && send_datagram && + left >= ngtcp2_pkt_datagram_framelen((size_t)datalen)) { + if (conn->callbacks.ack_datagram || conn->callbacks.lost_datagram) { + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + nfrc->fr.datagram.type = NGTCP2_FRAME_DATAGRAM_LEN; + nfrc->fr.datagram.dgram_id = vmsg->datagram.dgram_id; + nfrc->fr.datagram.datacnt = vmsg->datagram.datacnt; + nfrc->fr.datagram.data = (ngtcp2_vec *)vmsg->datagram.data; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &nfrc->fr); + assert(rv == 0); + + /* Because DATAGRAM will not be retransmitted, we do not use + data anymore. Just nullify it. The only reason to keep + track a frame is keep dgram_id to pass it to + ngtcp2_ack_datagram or ngtcp2_lost_datagram callbacks. */ + nfrc->fr.datagram.datacnt = 0; + nfrc->fr.datagram.data = NULL; + + *pfrc = nfrc; + pfrc = &(*pfrc)->next; + } else { + lfr.datagram.type = NGTCP2_FRAME_DATAGRAM_LEN; + lfr.datagram.datacnt = vmsg->datagram.datacnt; + lfr.datagram.data = (ngtcp2_vec *)vmsg->datagram.data; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + assert(rv == 0); + } + + pkt_empty = 0; + rtb_entry_flags |= + NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | NGTCP2_RTB_ENTRY_FLAG_DATAGRAM; + + if (vmsg->datagram.paccepted) { + *vmsg->datagram.paccepted = 1; + } + } else { + send_datagram = 0; + } + + if (pkt_empty) { + assert(rv == 0 || NGTCP2_ERR_NOBUF == rv); + if (rv == 0 && stream_blocked && ngtcp2_conn_get_max_data_left(conn)) { + return NGTCP2_ERR_STREAM_DATA_BLOCKED; + } + + keep_alive_expired = conn_keep_alive_expired(conn, ts); + + if (conn->pktns.rtb.probe_pkt_left == 0 && !keep_alive_expired && + !require_padding) { + return 0; + } + } else if (write_more) { + conn->pkt.pfrc = pfrc; + conn->pkt.pkt_empty = pkt_empty; + conn->pkt.rtb_entry_flags = rtb_entry_flags; + conn->pkt.hd_logged = hd_logged; + conn->flags |= NGTCP2_CONN_FLAG_PPE_PENDING; + + assert(vmsg); + + switch (vmsg->type) { + case NGTCP2_VMSG_TYPE_STREAM: + if (send_stream) { + if (ngtcp2_ppe_left(ppe)) { + return NGTCP2_ERR_WRITE_MORE; + } + break; + } + + if (ngtcp2_conn_get_max_data_left(conn) && stream_blocked) { + return NGTCP2_ERR_STREAM_DATA_BLOCKED; + } + break; + case NGTCP2_VMSG_TYPE_DATAGRAM: + if (send_datagram && ngtcp2_ppe_left(ppe)) { + return NGTCP2_ERR_WRITE_MORE; + } + /* If DATAGRAM cannot be written due to insufficient space, + continue to create a packet with the hope that application + calls ngtcp2_conn_writev_datagram again. */ + break; + default: + ngtcp2_unreachable(); + } + } + + if (!(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + if (pktns->tx.num_non_ack_pkt >= NGTCP2_MAX_NON_ACK_TX_PKT || + keep_alive_expired || conn->pktns.rtb.probe_pkt_left) { + lfr.type = NGTCP2_FRAME_PING; + + rv = conn_ppe_write_frame_hd_log(conn, ppe, &hd_logged, hd, &lfr); + if (rv != 0) { + assert(rv == NGTCP2_ERR_NOBUF); + /* TODO If buffer is too small, PING cannot be written if + packet is still empty. */ + } else { + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING; + if (conn->pktns.rtb.probe_pkt_left) { + rtb_entry_flags |= NGTCP2_RTB_ENTRY_FLAG_PROBE; + } + pktns->tx.num_non_ack_pkt = 0; + } + } else { + ++pktns->tx.num_non_ack_pkt; + } + } else { + pktns->tx.num_non_ack_pkt = 0; + } + + /* TODO Push STREAM frame back to ngtcp2_strm if there is an error + before ngtcp2_rtb_entry is safely created and added. */ + if (require_padding || + /* Making full sized packet will help GSO a bit */ + ngtcp2_ppe_left(ppe) < 10) { + lfr.padding.len = ngtcp2_ppe_padding(ppe); + } else { + lfr.padding.len = ngtcp2_ppe_padding_size(ppe, min_pktlen); + } + + if (lfr.padding.len) { + lfr.type = NGTCP2_FRAME_PADDING; + padded = 1; + ngtcp2_log_tx_fr(&conn->log, hd, &lfr); + ngtcp2_qlog_write_frame(&conn->qlog, &lfr); + } + + nwrite = ngtcp2_ppe_final(ppe, NULL); + if (nwrite < 0) { + assert(ngtcp2_err_is_fatal((int)nwrite)); + return nwrite; + } + + ++cc->ckm->use_count; + + ngtcp2_qlog_pkt_sent_end(&conn->qlog, hd, (size_t)nwrite); + + /* TODO ack-eliciting vs needs-tracking */ + /* probe packet needs tracking but it does not need ACK, could be lost. */ + if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) { + if (pi) { + conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, hd, ts); + } + + rv = ngtcp2_rtb_entry_objalloc_new(&ent, hd, NULL, ts, (size_t)nwrite, + rtb_entry_flags, + &conn->rtb_entry_objalloc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal((int)nwrite)); + return rv; + } + + if (*pfrc != pktns->tx.frq) { + ent->frc = pktns->tx.frq; + pktns->tx.frq = *pfrc; + *pfrc = NULL; + } + + if ((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) && + pktns->rtb.num_ack_eliciting == 0 && conn->cc.event) { + conn->cc.event(&conn->cc, &conn->cstat, NGTCP2_CC_EVENT_TYPE_TX_START, + ts); + } + + rv = conn_on_pkt_sent(conn, &pktns->rtb, ent); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_rtb_entry_objalloc_del(ent, &conn->rtb_entry_objalloc, + &conn->frc_objalloc, conn->mem); + return rv; + } + + if (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + if (conn->cc.on_pkt_sent) { + conn->cc.on_pkt_sent( + &conn->cc, &conn->cstat, + ngtcp2_cc_pkt_init(&cc_pkt, hd->pkt_num, (size_t)nwrite, + NGTCP2_PKTNS_ID_APPLICATION, ts, ent->rst.lost, + ent->rst.tx_in_flight, ent->rst.is_app_limited)); + } + + if (conn->flags & NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE) { + conn_restart_timer_on_write(conn, ts); + } + } + } else if (pi && conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) { + conn_handle_tx_ecn(conn, pi, NULL, pktns, hd, ts); + } + + conn->flags &= (uint32_t)~NGTCP2_CONN_FLAG_PPE_PENDING; + + if (pktns->rtb.probe_pkt_left && + (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + --pktns->rtb.probe_pkt_left; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "probe pkt size=%td", + nwrite); + } + + conn_update_keep_alive_last_ts(conn, ts); + + conn->dcid.current.bytes_sent += (uint64_t)nwrite; + + conn->tx.pacing.pktlen += (size_t)nwrite; + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + ++pktns->tx.last_pkt_num; + + return nwrite; +} + +ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, + uint8_t type, uint8_t flags, const ngtcp2_cid *dcid, ngtcp2_frame *fr, + uint16_t rtb_entry_flags, const ngtcp2_path *path, ngtcp2_tstamp ts) { + int rv; + ngtcp2_ppe ppe; + ngtcp2_pkt_hd hd; + ngtcp2_frame lfr; + ngtcp2_ssize nwrite; + ngtcp2_crypto_cc cc; + ngtcp2_pktns *pktns; + uint8_t hd_flags; + ngtcp2_rtb_entry *rtbent; + int padded = 0; + const ngtcp2_cid *scid; + uint32_t version; + + switch (type) { + case NGTCP2_PKT_INITIAL: + pktns = conn->in_pktns; + hd_flags = conn_pkt_flags_long(conn); + scid = &conn->oscid; + version = conn->negotiated_version ? conn->negotiated_version + : conn->client_chosen_version; + if (version == conn->client_chosen_version) { + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + } else { + assert(version == conn->vneg.version); + + cc.ckm = conn->vneg.tx.ckm; + cc.hp_ctx = conn->vneg.tx.hp_ctx; + } + break; + case NGTCP2_PKT_HANDSHAKE: + pktns = conn->hs_pktns; + hd_flags = conn_pkt_flags_long(conn); + scid = &conn->oscid; + version = conn->negotiated_version; + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + break; + case NGTCP2_PKT_1RTT: + pktns = &conn->pktns; + hd_flags = conn_pkt_flags_short(conn); + scid = NULL; + version = conn->negotiated_version; + cc.ckm = pktns->crypto.tx.ckm; + cc.hp_ctx = pktns->crypto.tx.hp_ctx; + break; + default: + /* We don't support 0-RTT packet in this function. */ + ngtcp2_unreachable(); + } + + cc.aead = pktns->crypto.ctx.aead; + cc.hp = pktns->crypto.ctx.hp; + cc.encrypt = conn->callbacks.encrypt; + cc.hp_mask = conn->callbacks.hp_mask; + + ngtcp2_pkt_hd_init(&hd, hd_flags, type, dcid, scid, + pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns), + version, 0); + + ngtcp2_ppe_init(&ppe, dest, destlen, &cc); + + rv = ngtcp2_ppe_encode_hd(&ppe, &hd); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return 0; + } + + if (!ngtcp2_ppe_ensure_hp_sample(&ppe)) { + return 0; + } + + ngtcp2_log_tx_pkt_hd(&conn->log, &hd); + ngtcp2_qlog_pkt_sent_start(&conn->qlog); + + rv = conn_ppe_write_frame(conn, &ppe, &hd, fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return 0; + } + + lfr.type = NGTCP2_FRAME_PADDING; + if (flags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING) { + lfr.padding.len = ngtcp2_ppe_padding(&ppe); + } else { + switch (fr->type) { + case NGTCP2_FRAME_PATH_CHALLENGE: + case NGTCP2_FRAME_PATH_RESPONSE: + if (!conn->server || destlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + lfr.padding.len = ngtcp2_ppe_padding(&ppe); + } else { + lfr.padding.len = 0; + } + break; + default: + if (type == NGTCP2_PKT_1RTT) { + lfr.padding.len = + ngtcp2_ppe_padding_size(&ppe, conn_min_short_pktlen(conn)); + } else { + lfr.padding.len = ngtcp2_ppe_padding_hp_sample(&ppe); + } + } + } + if (lfr.padding.len) { + padded = 1; + ngtcp2_log_tx_fr(&conn->log, &hd, &lfr); + ngtcp2_qlog_write_frame(&conn->qlog, &lfr); + } + + nwrite = ngtcp2_ppe_final(&ppe, NULL); + if (nwrite < 0) { + return nwrite; + } + + if (type == NGTCP2_PKT_1RTT) { + ++cc.ckm->use_count; + } + + ngtcp2_qlog_pkt_sent_end(&conn->qlog, &hd, (size_t)nwrite); + + /* Do this when we are sure that there is no error. */ + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + ngtcp2_acktr_commit_ack(&pktns->acktr); + ngtcp2_acktr_add_ack(&pktns->acktr, hd.pkt_num, fr->ack.largest_ack); + if (type == NGTCP2_PKT_1RTT) { + conn_handle_unconfirmed_key_update_from_remote(conn, fr->ack.largest_ack, + ts); + } + break; + } + + if (((rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) || padded) && + (!path || ngtcp2_path_eq(&conn->dcid.current.ps.path, path))) { + if (pi && !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE)) { + conn_handle_tx_ecn(conn, pi, &rtb_entry_flags, pktns, &hd, ts); + } + + rv = ngtcp2_rtb_entry_objalloc_new(&rtbent, &hd, NULL, ts, (size_t)nwrite, + rtb_entry_flags, + &conn->rtb_entry_objalloc); + if (rv != 0) { + return rv; + } + + rv = conn_on_pkt_sent(conn, &pktns->rtb, rtbent); + if (rv != 0) { + ngtcp2_rtb_entry_objalloc_del(rtbent, &conn->rtb_entry_objalloc, + &conn->frc_objalloc, conn->mem); + return rv; + } + + if (rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + if (conn->flags & NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE) { + conn_restart_timer_on_write(conn, ts); + } + + if (pktns->rtb.probe_pkt_left && path && + ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + --pktns->rtb.probe_pkt_left; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "probe pkt size=%td", + nwrite); + } + } + } else if (pi && !(rtb_entry_flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) && + conn->tx.ecn.state == NGTCP2_ECN_STATE_CAPABLE) { + conn_handle_tx_ecn(conn, pi, NULL, pktns, &hd, ts); + } + + if (path && ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + conn_update_keep_alive_last_ts(conn, ts); + } + + if (!padded) { + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + break; + default: + conn->tx.pacing.pktlen += (size_t)nwrite; + } + } else { + conn->tx.pacing.pktlen += (size_t)nwrite; + } + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + ++pktns->tx.last_pkt_num; + + return nwrite; +} + +/* + * conn_process_early_rtb makes any pending 0RTT packet 1RTT packet. + */ +static void conn_process_early_rtb(ngtcp2_conn *conn) { + ngtcp2_rtb_entry *ent; + ngtcp2_rtb *rtb = &conn->pktns.rtb; + ngtcp2_ksl_it it; + + for (it = ngtcp2_rtb_head(rtb); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ent = ngtcp2_ksl_it_get(&it); + + if ((ent->hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) == 0 || + ent->hd.type != NGTCP2_PKT_0RTT) { + continue; + } + + /* 0-RTT packet is retransmitted as a 1RTT packet. */ + ent->hd.flags &= (uint8_t)~NGTCP2_PKT_FLAG_LONG_FORM; + ent->hd.type = NGTCP2_PKT_1RTT; + } +} + +/* + * conn_handshake_remnants_left returns nonzero if there may be + * handshake packets the local endpoint has to send, including new + * packets and lost ones. + */ +static int conn_handshake_remnants_left(ngtcp2_conn *conn) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + + return !conn_is_handshake_completed(conn) || + (in_pktns && (in_pktns->rtb.num_pto_eliciting || + ngtcp2_ksl_len(&in_pktns->crypto.tx.frq))) || + (hs_pktns && (hs_pktns->rtb.num_pto_eliciting || + ngtcp2_ksl_len(&hs_pktns->crypto.tx.frq))); +} + +/* + * conn_retire_dcid_seq retires destination connection ID denoted by + * |seq|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CONNECTION_ID_LIMIT + * The number of unacknowledged retirement exceeds the limit. + */ +static int conn_retire_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_frame_chain *nfrc; + int rv; + + rv = ngtcp2_conn_track_retired_dcid_seq(conn, seq); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + nfrc->fr.type = NGTCP2_FRAME_RETIRE_CONNECTION_ID; + nfrc->fr.retire_connection_id.seq = seq; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + + return 0; +} + +/* + * conn_retire_dcid retires |dcid|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_retire_dcid(ngtcp2_conn *conn, const ngtcp2_dcid *dcid, + ngtcp2_tstamp ts) { + ngtcp2_ringbuf *rb = &conn->dcid.retired.rb; + ngtcp2_dcid *dest, *stale_dcid; + int rv; + + assert(dcid->cid.datalen); + + if (ngtcp2_ringbuf_full(rb)) { + stale_dcid = ngtcp2_ringbuf_get(rb, 0); + rv = conn_call_deactivate_dcid(conn, stale_dcid); + if (rv != 0) { + return rv; + } + + ngtcp2_ringbuf_pop_front(rb); + } + + dest = ngtcp2_ringbuf_push_back(rb); + ngtcp2_dcid_copy(dest, dcid); + dest->retired_ts = ts; + + return conn_retire_dcid_seq(conn, dcid->seq); +} + +/* + * conn_bind_dcid stores the DCID to |*pdcid| bound to |path|. If + * such DCID is not found, bind the new DCID to |path| and stores it + * to |*pdcid|. If a remote endpoint uses zero-length connection ID, + * the pointer to conn->dcid.current is assigned to |*pdcid|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CONN_ID_BLOCKED + * No unused DCID is available + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_bind_dcid(ngtcp2_conn *conn, ngtcp2_dcid **pdcid, + const ngtcp2_path *path, ngtcp2_tstamp ts) { + ngtcp2_dcid *dcid, *ndcid; + ngtcp2_cid cid; + size_t i, len; + int rv; + + assert(!ngtcp2_path_eq(&conn->dcid.current.ps.path, path)); + assert(!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path)); + assert(!conn->pv || !(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) || + !ngtcp2_path_eq(&conn->pv->fallback_dcid.ps.path, path)); + + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + + if (ngtcp2_path_eq(&dcid->ps.path, path)) { + *pdcid = dcid; + return 0; + } + } + + if (conn->dcid.current.cid.datalen == 0) { + ndcid = ngtcp2_ringbuf_push_back(&conn->dcid.bound.rb); + ngtcp2_cid_zero(&cid); + ngtcp2_dcid_init(ndcid, ++conn->dcid.zerolen_seq, &cid, NULL); + ngtcp2_dcid_set_path(ndcid, path); + + *pdcid = ndcid; + + return 0; + } + + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return NGTCP2_ERR_CONN_ID_BLOCKED; + } + + if (ngtcp2_ringbuf_full(&conn->dcid.bound.rb)) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, 0); + rv = conn_retire_dcid(conn, dcid, ts); + if (rv != 0) { + return rv; + } + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ndcid = ngtcp2_ringbuf_push_back(&conn->dcid.bound.rb); + + ngtcp2_dcid_copy(ndcid, dcid); + ndcid->bound_ts = ts; + ngtcp2_dcid_set_path(ndcid, path); + + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + *pdcid = ndcid; + + return 0; +} + +static int conn_start_pmtud(ngtcp2_conn *conn) { + int rv; + size_t hard_max_udp_payload_size; + + assert(!conn->local.settings.no_pmtud); + assert(!conn->pmtud); + assert(conn_is_handshake_completed(conn)); + assert(conn->remote.transport_params); + assert(conn->remote.transport_params->max_udp_payload_size >= + NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + hard_max_udp_payload_size = (size_t)ngtcp2_min( + conn->remote.transport_params->max_udp_payload_size, + (uint64_t)conn->local.settings.max_tx_udp_payload_size); + + rv = ngtcp2_pmtud_new(&conn->pmtud, conn->dcid.current.max_udp_payload_size, + hard_max_udp_payload_size, + conn->pktns.tx.last_pkt_num + 1, conn->mem); + if (rv != 0) { + return rv; + } + + if (ngtcp2_pmtud_finished(conn->pmtud)) { + ngtcp2_conn_stop_pmtud(conn); + } + + return 0; +} + +int ngtcp2_conn_start_pmtud(ngtcp2_conn *conn) { + return conn_start_pmtud(conn); +} + +void ngtcp2_conn_stop_pmtud(ngtcp2_conn *conn) { + if (!conn->pmtud) { + return; + } + + ngtcp2_pmtud_del(conn->pmtud); + + conn->pmtud = NULL; +} + +static ngtcp2_ssize conn_write_pmtud_probe(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, ngtcp2_tstamp ts) { + size_t probelen; + ngtcp2_ssize nwrite; + ngtcp2_frame lfr; + + assert(conn->pmtud); + assert(!ngtcp2_pmtud_finished(conn->pmtud)); + + if (!ngtcp2_pmtud_require_probe(conn->pmtud)) { + return 0; + } + + probelen = ngtcp2_pmtud_probelen(conn->pmtud); + if (probelen > destlen) { + return 0; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "sending PMTUD probe packet len=%zu", probelen); + + lfr.type = NGTCP2_FRAME_PING; + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, probelen, NGTCP2_PKT_1RTT, + NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING, &conn->dcid.current.cid, &lfr, + NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING | + NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE, + NULL, ts); + if (nwrite < 0) { + return nwrite; + } + + assert(nwrite); + + ngtcp2_pmtud_probe_sent(conn->pmtud, conn_compute_pto(conn, &conn->pktns), + ts); + + return nwrite; +} + +/* + * conn_stop_pv stops the path validation which is currently running. + * This function does nothing if no path validation is currently being + * performed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_stop_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + int rv = 0; + ngtcp2_pv *pv = conn->pv; + + if (pv == NULL) { + return 0; + } + + if (pv->dcid.cid.datalen && pv->dcid.seq != conn->dcid.current.seq) { + rv = conn_retire_dcid(conn, &pv->dcid, ts); + if (rv != 0) { + goto fin; + } + } + + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.cid.datalen && + pv->fallback_dcid.seq != conn->dcid.current.seq && + pv->fallback_dcid.seq != pv->dcid.seq) { + rv = conn_retire_dcid(conn, &pv->fallback_dcid, ts); + if (rv != 0) { + goto fin; + } + } + +fin: + ngtcp2_pv_del(pv); + conn->pv = NULL; + + return rv; +} + +/* + * conn_abort_pv aborts the current path validation and frees + * resources allocated for it. This function assumes that conn->pv is + * not NULL. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_abort_pv(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_pv *pv = conn->pv; + int rv; + + assert(pv); + + if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) { + rv = conn_call_path_validation(conn, pv, + NGTCP2_PATH_VALIDATION_RESULT_ABORTED); + if (rv != 0) { + return rv; + } + } + + return conn_stop_pv(conn, ts); +} + +static size_t conn_shape_udp_payload(ngtcp2_conn *conn, const ngtcp2_dcid *dcid, + size_t payloadlen) { + if (conn->remote.transport_params && + conn->remote.transport_params->max_udp_payload_size) { + assert(conn->remote.transport_params->max_udp_payload_size >= + NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + payloadlen = + (size_t)ngtcp2_min((uint64_t)payloadlen, + conn->remote.transport_params->max_udp_payload_size); + } + + payloadlen = + ngtcp2_min(payloadlen, conn->local.settings.max_tx_udp_payload_size); + + if (conn->local.settings.no_tx_udp_payload_size_shaping) { + return payloadlen; + } + + return ngtcp2_min(payloadlen, dcid->max_udp_payload_size); +} + +static void conn_reset_congestion_state(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +/* + * conn_on_path_validation_failed is called when path validation + * fails. This function may delete |pv|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_on_path_validation_failed(ngtcp2_conn *conn, ngtcp2_pv *pv, + ngtcp2_tstamp ts) { + int rv; + + if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) { + rv = conn_call_path_validation(conn, pv, + NGTCP2_PATH_VALIDATION_RESULT_FAILURE); + if (rv != 0) { + return rv; + } + } + + if (pv->flags & NGTCP2_PV_FLAG_MTU_PROBE) { + return NGTCP2_ERR_NO_VIABLE_PATH; + } + + if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) { + ngtcp2_dcid_copy(&conn->dcid.current, &pv->fallback_dcid); + conn_reset_congestion_state(conn, ts); + } + + return conn_stop_pv(conn, ts); +} + +/* + * conn_write_path_challenge writes a packet which includes + * PATH_CHALLENGE frame into |dest| of length |destlen|. + * + * This function returns the number of bytes written to |dest|, or one + * of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize conn_write_path_challenge(ngtcp2_conn *conn, + ngtcp2_path *path, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_tstamp ts) { + ngtcp2_ssize nwrite; + ngtcp2_tstamp expiry; + ngtcp2_pv *pv = conn->pv; + ngtcp2_frame lfr; + ngtcp2_duration timeout; + uint8_t flags; + uint64_t tx_left; + int rv; + + if (ngtcp2_pv_validation_timed_out(pv, ts)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV, + "path validation was timed out"); + rv = conn_on_path_validation_failed(conn, pv, ts); + if (rv != 0) { + return rv; + } + + /* We might set path to the one which we just failed validate. + Set it to the current path here. */ + if (path) { + ngtcp2_path_copy(path, &conn->dcid.current.ps.path); + } + + return 0; + } + + ngtcp2_pv_handle_entry_expiry(pv, ts); + + if (!ngtcp2_pv_should_send_probe(pv)) { + return 0; + } + + rv = conn_call_get_path_challenge_data(conn, lfr.path_challenge.data); + if (rv != 0) { + return rv; + } + + lfr.type = NGTCP2_FRAME_PATH_CHALLENGE; + + timeout = conn_compute_pto(conn, &conn->pktns); + timeout = ngtcp2_max(timeout, 3 * conn->cstat.initial_rtt); + expiry = ts + timeout * (1ULL << pv->round); + + destlen = ngtcp2_min(destlen, NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + if (conn->server) { + if (!(pv->dcid.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + tx_left = conn_server_tx_left(conn, &pv->dcid); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, tx_left); + if (destlen == 0) { + return 0; + } + } + + if (destlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + flags = NGTCP2_PV_ENTRY_FLAG_UNDERSIZED; + } else { + flags = NGTCP2_PV_ENTRY_FLAG_NONE; + } + } else { + flags = NGTCP2_PV_ENTRY_FLAG_NONE; + } + + ngtcp2_pv_add_entry(pv, lfr.path_challenge.data, expiry, flags, ts); + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE, + &pv->dcid.cid, &lfr, + NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING | NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING, + &pv->dcid.ps.path, ts); + if (nwrite <= 0) { + return nwrite; + } + + if (path) { + ngtcp2_path_copy(path, &pv->dcid.ps.path); + } + + if (ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path)) { + conn->dcid.current.bytes_sent += (uint64_t)nwrite; + } else { + pv->dcid.bytes_sent += (uint64_t)nwrite; + } + + return nwrite; +} + +/* + * conn_write_path_response writes a packet which includes + * PATH_RESPONSE frame into |dest| of length |destlen|. + * + * This function returns the number of bytes written to |dest|, or one + * of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static ngtcp2_ssize conn_write_path_response(ngtcp2_conn *conn, + ngtcp2_path *path, + ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, ngtcp2_tstamp ts) { + ngtcp2_pv *pv = conn->pv; + ngtcp2_path_challenge_entry *pcent = NULL; + ngtcp2_dcid *dcid = NULL; + ngtcp2_frame lfr; + ngtcp2_ssize nwrite; + int rv; + uint64_t tx_left; + + for (; ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb);) { + pcent = ngtcp2_ringbuf_get(&conn->rx.path_challenge.rb, 0); + + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, &pcent->ps.path)) { + /* Send PATH_RESPONSE from conn_write_pkt. */ + return 0; + } + + if (pv) { + if (ngtcp2_path_eq(&pv->dcid.ps.path, &pcent->ps.path)) { + dcid = &pv->dcid; + break; + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + ngtcp2_path_eq(&pv->fallback_dcid.ps.path, &pcent->ps.path)) { + dcid = &pv->fallback_dcid; + break; + } + } + + if (conn->server) { + break; + } + + /* Client does not expect to respond to path validation against + unknown path */ + ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb); + pcent = NULL; + } + + if (pcent == NULL) { + return 0; + } + + if (dcid == NULL) { + /* client is expected to have |path| in conn->dcid.current or + conn->pv. */ + assert(conn->server); + + rv = conn_bind_dcid(conn, &dcid, &pcent->ps.path, ts); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + return 0; + } + } + + destlen = ngtcp2_min(destlen, NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + if (conn->server && !(dcid->flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + tx_left = conn_server_tx_left(conn, dcid); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, tx_left); + if (destlen == 0) { + return 0; + } + } + + lfr.type = NGTCP2_FRAME_PATH_RESPONSE; + memcpy(lfr.path_response.data, pcent->data, sizeof(lfr.path_response.data)); + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE, + &dcid->cid, &lfr, NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING, &pcent->ps.path, + ts); + if (nwrite <= 0) { + return nwrite; + } + + if (path) { + ngtcp2_path_copy(path, &pcent->ps.path); + } + + ngtcp2_ringbuf_pop_front(&conn->rx.path_challenge.rb); + + dcid->bytes_sent += (uint64_t)nwrite; + + return nwrite; +} + +ngtcp2_ssize ngtcp2_conn_write_pkt_versioned(ngtcp2_conn *conn, + ngtcp2_path *path, + int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, ngtcp2_tstamp ts) { + return ngtcp2_conn_writev_stream_versioned( + conn, path, pkt_info_version, pi, dest, destlen, + /* pdatalen = */ NULL, NGTCP2_WRITE_STREAM_FLAG_NONE, + /* stream_id = */ -1, + /* datav = */ NULL, /* datavcnt = */ 0, ts); +} + +/* + * conn_on_version_negotiation is called when Version Negotiation + * packet is received. The function decodes the data in the buffer + * pointed by |payload| whose length is |payloadlen| as Version + * Negotiation packet payload. The packet header is given in |hd|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_INVALID_ARGUMENT + * Packet payload is badly formatted. + */ +static int conn_on_version_negotiation(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + const uint8_t *payload, + size_t payloadlen) { + uint32_t sv[16]; + uint32_t *p; + int rv = 0; + size_t nsv; + size_t i; + + if (payloadlen % sizeof(uint32_t)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + /* Version Negotiation packet is ignored if client has reacted upon + Version Negotiation packet. */ + if (conn->local.settings.original_version != conn->client_chosen_version) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (payloadlen > sizeof(sv)) { + p = ngtcp2_mem_malloc(conn->mem, payloadlen); + if (p == NULL) { + return NGTCP2_ERR_NOMEM; + } + } else { + p = sv; + } + + nsv = ngtcp2_pkt_decode_version_negotiation(p, payload, payloadlen); + + ngtcp2_log_rx_vn(&conn->log, hd, p, nsv); + + ngtcp2_qlog_version_negotiation_pkt_received(&conn->qlog, hd, p, nsv); + + if (!ngtcp2_is_reserved_version(conn->local.settings.original_version)) { + for (i = 0; i < nsv; ++i) { + if (p[i] == conn->local.settings.original_version) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "ignore Version Negotiation because it contains the " + "original version"); + + rv = NGTCP2_ERR_INVALID_ARGUMENT; + goto fin; + } + } + } + + rv = conn_call_recv_version_negotiation(conn, hd, p, nsv); + if (rv != 0) { + goto fin; + } + +fin: + if (p != sv) { + ngtcp2_mem_free(conn->mem, p); + } + + return rv; +} + +static uint64_t conn_tx_strmq_first_cycle(ngtcp2_conn *conn) { + ngtcp2_strm *strm; + + if (ngtcp2_pq_empty(&conn->tx.strmq)) { + return 0; + } + + strm = ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe); + return strm->cycle; +} + +uint64_t ngtcp2_conn_tx_strmq_first_cycle(ngtcp2_conn *conn) { + ngtcp2_strm *strm; + + if (ngtcp2_pq_empty(&conn->tx.strmq)) { + return 0; + } + + strm = ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe); + return strm->cycle; +} + +int ngtcp2_conn_resched_frames(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_frame_chain **pfrc) { + ngtcp2_frame_chain **first = pfrc; + ngtcp2_frame_chain *frc; + ngtcp2_stream *sfr; + ngtcp2_strm *strm; + int rv; + int streamfrq_empty; + + if (*pfrc == NULL) { + return 0; + } + + for (; *pfrc;) { + switch ((*pfrc)->fr.type) { + case NGTCP2_FRAME_STREAM: + frc = *pfrc; + + *pfrc = frc->next; + frc->next = NULL; + sfr = &frc->fr.stream; + + strm = ngtcp2_conn_find_stream(conn, sfr->stream_id); + if (!strm) { + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + break; + } + streamfrq_empty = ngtcp2_strm_streamfrq_empty(strm); + rv = ngtcp2_strm_streamfrq_push(strm, frc); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + if (!ngtcp2_strm_is_tx_queued(strm)) { + strm->cycle = conn_tx_strmq_first_cycle(conn); + rv = ngtcp2_conn_tx_strmq_push(conn, strm); + if (rv != 0) { + return rv; + } + } + if (streamfrq_empty) { + ++conn->tx.strmq_nretrans; + } + break; + case NGTCP2_FRAME_CRYPTO: + frc = *pfrc; + + *pfrc = frc->next; + frc->next = NULL; + + rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, + &frc->fr.crypto.offset, frc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + break; + default: + pfrc = &(*pfrc)->next; + } + } + + *pfrc = pktns->tx.frq; + pktns->tx.frq = *first; + + return 0; +} + +/* + * conn_on_retry is called when Retry packet is received. The + * function decodes the data in the buffer pointed by |pkt| whose + * length is |pktlen| as Retry packet. The length of long packet + * header is given in |hdpktlen|. |pkt| includes packet header. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_INVALID_ARGUMENT + * Packet payload is badly formatted. + * NGTCP2_ERR_PROTO + * ODCID does not match; or Token is empty. + */ +static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + size_t hdpktlen, const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_pkt_retry retry; + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_rtb *rtb = &conn->pktns.rtb; + ngtcp2_rtb *in_rtb; + uint8_t cidbuf[sizeof(retry.odcid.data) * 2 + 1]; + ngtcp2_vec *token; + + if (!in_pktns || conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) { + return 0; + } + + in_rtb = &in_pktns->rtb; + + rv = ngtcp2_pkt_decode_retry(&retry, pkt + hdpktlen, pktlen - hdpktlen); + if (rv != 0) { + return rv; + } + + retry.odcid = conn->dcid.current.cid; + + rv = ngtcp2_pkt_verify_retry_tag( + conn->client_chosen_version, &retry, pkt, pktlen, conn->callbacks.encrypt, + &conn->crypto.retry_aead, &conn->crypto.retry_aead_ctx); + if (rv != 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "unable to verify Retry packet integrity"); + return rv; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, "odcid=0x%s", + (const char *)ngtcp2_encode_hex(cidbuf, retry.odcid.data, + retry.odcid.datalen)); + + if (retry.token.len == 0) { + return NGTCP2_ERR_PROTO; + } + + if (ngtcp2_cid_eq(&conn->dcid.current.cid, &hd->scid)) { + return 0; + } + + ngtcp2_qlog_retry_pkt_received(&conn->qlog, hd, &retry); + + /* DCID must be updated before invoking callback because client + generates new initial keys there. */ + conn->dcid.current.cid = hd->scid; + conn->retry_scid = hd->scid; + + conn->flags |= NGTCP2_CONN_FLAG_RECV_RETRY; + + rv = conn_call_recv_retry(conn, hd); + if (rv != 0) { + return rv; + } + + conn->state = NGTCP2_CS_CLIENT_INITIAL; + + /* Just freeing memory is dangerous because we might free twice. */ + + rv = ngtcp2_rtb_remove_all(rtb, conn, &conn->pktns, &conn->cstat); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_rtb_remove_all(in_rtb, conn, in_pktns, &conn->cstat); + if (rv != 0) { + return rv; + } + + token = &conn->local.settings.token; + + ngtcp2_mem_free(conn->mem, token->base); + token->base = NULL; + token->len = 0; + + token->base = ngtcp2_mem_malloc(conn->mem, retry.token.len); + if (token->base == NULL) { + return NGTCP2_ERR_NOMEM; + } + token->len = retry.token.len; + + ngtcp2_cpymem(token->base, retry.token.base, retry.token.len); + + reset_conn_stat_recovery(&conn->cstat); + conn_reset_congestion_state(conn, ts); + conn_reset_ecn_validation_state(conn); + + return 0; +} + +int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { + return ngtcp2_rtb_detect_lost_pkt(&pktns->rtb, conn, pktns, cstat, ts); +} + +/* + * conn_recv_ack processes received ACK frame |fr|. |pkt_ts| is the + * timestamp when packet is received. |ts| should be the current + * time. Usually they are the same, but for buffered packets, + * |pkt_ts| would be earlier than |ts|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed. + * NGTCP2_ERR_PROTO + * |fr| acknowledges a packet this endpoint has not sent. + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + */ +static int conn_recv_ack(ngtcp2_conn *conn, ngtcp2_pktns *pktns, ngtcp2_ack *fr, + ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts) { + int rv; + ngtcp2_frame_chain *frc = NULL; + ngtcp2_ssize num_acked; + ngtcp2_conn_stat *cstat = &conn->cstat; + + if (pktns->tx.last_pkt_num < fr->largest_ack) { + return NGTCP2_ERR_PROTO; + } + + rv = ngtcp2_pkt_validate_ack(fr); + if (rv != 0) { + return rv; + } + + ngtcp2_acktr_recv_ack(&pktns->acktr, fr); + + num_acked = ngtcp2_rtb_recv_ack(&pktns->rtb, fr, &conn->cstat, conn, pktns, + pkt_ts, ts); + if (num_acked < 0) { + /* TODO assert this */ + assert(ngtcp2_err_is_fatal((int)num_acked)); + ngtcp2_frame_chain_list_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return (int)num_acked; + } + + if (num_acked == 0) { + return 0; + } + + pktns->rtb.probe_pkt_left = 0; + + if (cstat->pto_count && + (conn->server || (conn->flags & NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED))) { + /* Reset PTO count but no less than 2 to avoid frequent probe + packet transmission. */ + cstat->pto_count = ngtcp2_min(cstat->pto_count, 2); + } + + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + return 0; +} + +/* + * conn_assign_recved_ack_delay_unscaled assigns + * fr->ack_delay_unscaled. + */ +static void assign_recved_ack_delay_unscaled(ngtcp2_ack *fr, + uint64_t ack_delay_exponent) { + fr->ack_delay_unscaled = + fr->ack_delay * (1ULL << ack_delay_exponent) * NGTCP2_MICROSECONDS; +} + +/* + * conn_recv_max_stream_data processes received MAX_STREAM_DATA frame + * |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_STREAM_STATE + * Stream ID indicates that it is a local stream, and the local + * endpoint has not initiated it; or stream is peer initiated + * unidirectional stream. + * NGTCP2_ERR_STREAM_LIMIT + * Stream ID exceeds allowed limit. + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_recv_max_stream_data(ngtcp2_conn *conn, + const ngtcp2_max_stream_data *fr) { + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; + int local_stream = conn_local_stream(conn, fr->stream_id); + int bidi = bidi_stream(fr->stream_id); + int rv; + + if (bidi) { + if (local_stream) { + if (conn->local.bidi.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + } else if (conn->remote.bidi.max_streams < + ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.bidi.idtr; + } else { + if (!local_stream || conn->local.uni.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + + idtr = &conn->remote.uni.idtr; + } + + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { + /* Stream has been closed. */ + return 0; + } + + rv = ngtcp2_idtr_open(idtr, fr->stream_id); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); + /* Stream has been closed. */ + return 0; + } + + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + + rv = conn_call_stream_open(conn, strm); + if (rv != 0) { + return rv; + } + } + + if (strm->tx.max_offset < fr->max_stream_data) { + strm->tx.max_offset = fr->max_stream_data; + + /* Don't call callback if stream is half-closed local */ + if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) { + return 0; + } + + rv = conn_call_extend_max_stream_data(conn, strm, fr->stream_id, + fr->max_stream_data); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +/* + * conn_recv_max_data processes received MAX_DATA frame |fr|. + */ +static void conn_recv_max_data(ngtcp2_conn *conn, const ngtcp2_max_data *fr) { + conn->tx.max_offset = ngtcp2_max(conn->tx.max_offset, fr->max_data); +} + +/* + * conn_buffer_pkt buffers |pkt| of length |pktlen|, chaining it from + * |*ppc|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_buffer_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + const ngtcp2_path *path, const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, size_t dgramlen, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_pkt_chain **ppc = &pktns->rx.buffed_pkts, *pc; + size_t i; + for (i = 0; *ppc && i < NGTCP2_MAX_NUM_BUFFED_RX_PKTS; + ppc = &(*ppc)->next, ++i) + ; + + if (i == NGTCP2_MAX_NUM_BUFFED_RX_PKTS) { + return 0; + } + + rv = + ngtcp2_pkt_chain_new(&pc, path, pi, pkt, pktlen, dgramlen, ts, conn->mem); + if (rv != 0) { + return rv; + } + + *ppc = pc; + + return 0; +} + +static int ensure_decrypt_buffer(ngtcp2_vec *vec, size_t n, size_t initial, + const ngtcp2_mem *mem) { + uint8_t *nbuf; + size_t len; + + if (vec->len >= n) { + return 0; + } + + len = vec->len == 0 ? initial : vec->len * 2; + for (; len < n; len *= 2) + ; + nbuf = ngtcp2_mem_realloc(mem, vec->base, len); + if (nbuf == NULL) { + return NGTCP2_ERR_NOMEM; + } + vec->base = nbuf; + vec->len = len; + + return 0; +} + +/* + * conn_ensure_decrypt_hp_buffer ensures that + * conn->crypto.decrypt_hp_buf has at least |n| bytes space. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_ensure_decrypt_hp_buffer(ngtcp2_conn *conn, size_t n) { + return ensure_decrypt_buffer(&conn->crypto.decrypt_hp_buf, n, 256, conn->mem); +} + +/* + * conn_ensure_decrypt_buffer ensures that conn->crypto.decrypt_buf + * has at least |n| bytes space. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_ensure_decrypt_buffer(ngtcp2_conn *conn, size_t n) { + return ensure_decrypt_buffer(&conn->crypto.decrypt_buf, n, 2048, conn->mem); +} + +/* + * decrypt_pkt decrypts the data pointed by |payload| whose length is + * |payloadlen|, and writes plaintext data to the buffer pointed by + * |dest|. The buffer pointed by |aad| is the Additional + * Authenticated Data, and its length is |aadlen|. |pkt_num| is used + * to create a nonce. |ckm| is the cryptographic key, and iv to use. + * |decrypt| is a callback function which actually decrypts a packet. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + * NGTCP2_ERR_DECRYPT + * Failed to decrypt a packet. + */ +static ngtcp2_ssize decrypt_pkt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const uint8_t *payload, size_t payloadlen, + const uint8_t *aad, size_t aadlen, + int64_t pkt_num, ngtcp2_crypto_km *ckm, + ngtcp2_decrypt decrypt) { + /* TODO nonce is limited to 64 bytes. */ + uint8_t nonce[64]; + int rv; + + assert(sizeof(nonce) >= ckm->iv.len); + + ngtcp2_crypto_create_nonce(nonce, ckm->iv.base, ckm->iv.len, pkt_num); + + rv = decrypt(dest, aead, &ckm->aead_ctx, payload, payloadlen, nonce, + ckm->iv.len, aad, aadlen); + + if (rv != 0) { + if (rv == NGTCP2_ERR_DECRYPT) { + return rv; + } + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + assert(payloadlen >= aead->max_overhead); + + return (ngtcp2_ssize)(payloadlen - aead->max_overhead); +} + +/* + * decrypt_hp decryptes packet header. The packet number starts at + * |pkt| + |pkt_num_offset|. The entire plaintext QUIC packet header + * will be written to the buffer pointed by |dest| whose capacity is + * |destlen|. + * + * This function returns the number of bytes written to |dest|, or one + * of the following negative error codes: + * + * NGTCP2_ERR_PROTO + * Packet is badly formatted + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed; or it does not return + * expected result. + */ +static ngtcp2_ssize +decrypt_hp(ngtcp2_pkt_hd *hd, uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const uint8_t *pkt, size_t pktlen, size_t pkt_num_offset, + const ngtcp2_crypto_cipher_ctx *hp_ctx, ngtcp2_hp_mask hp_mask) { + size_t sample_offset; + uint8_t *p = dest; + uint8_t mask[NGTCP2_HP_SAMPLELEN]; + size_t i; + int rv; + + assert(hp_mask); + + if (pkt_num_offset + 4 + NGTCP2_HP_SAMPLELEN > pktlen) { + return NGTCP2_ERR_PROTO; + } + + p = ngtcp2_cpymem(p, pkt, pkt_num_offset); + + sample_offset = pkt_num_offset + 4; + + rv = hp_mask(mask, hp, hp_ctx, pkt + sample_offset); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { + dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x0f)); + } else { + dest[0] = (uint8_t)(dest[0] ^ (mask[0] & 0x1f)); + if (dest[0] & NGTCP2_SHORT_KEY_PHASE_BIT) { + hd->flags |= NGTCP2_PKT_FLAG_KEY_PHASE; + } + } + + hd->pkt_numlen = (size_t)((dest[0] & NGTCP2_PKT_NUMLEN_MASK) + 1); + + for (i = 0; i < hd->pkt_numlen; ++i) { + *p++ = *(pkt + pkt_num_offset + i) ^ mask[i + 1]; + } + + hd->pkt_num = ngtcp2_get_pkt_num(p - hd->pkt_numlen, hd->pkt_numlen); + + return p - dest; +} + +/* + * conn_emit_pending_crypto_data delivers pending stream data to the + * application due to packet reordering. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed + * NGTCP2_ERR_CRYPTO + * TLS backend reported error + */ +static int conn_emit_pending_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + ngtcp2_strm *strm, + uint64_t rx_offset) { + size_t datalen; + const uint8_t *data; + int rv; + uint64_t offset; + + if (!strm->rx.rob) { + return 0; + } + + for (;;) { + datalen = ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset); + if (datalen == 0) { + assert(rx_offset == ngtcp2_strm_rx_offset(strm)); + return 0; + } + + offset = rx_offset; + rx_offset += datalen; + + rv = conn_call_recv_crypto_data(conn, crypto_level, offset, data, datalen); + if (rv != 0) { + return rv; + } + + ngtcp2_rob_pop(strm->rx.rob, rx_offset - datalen, datalen); + } +} + +/* + * conn_recv_connection_close is called when CONNECTION_CLOSE or + * APPLICATION_CLOSE frame is received. + */ +static int conn_recv_connection_close(ngtcp2_conn *conn, + ngtcp2_connection_close *fr) { + ngtcp2_connection_close_error *ccerr = &conn->rx.ccerr; + + conn->state = NGTCP2_CS_DRAINING; + if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) { + ccerr->type = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT; + } else { + ccerr->type = NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION; + } + ccerr->error_code = fr->error_code; + ccerr->frame_type = fr->frame_type; + + if (!fr->reasonlen) { + ccerr->reasonlen = 0; + + return 0; + } + + if (ccerr->reason == NULL) { + ccerr->reason = ngtcp2_mem_malloc( + conn->mem, NGTCP2_CONNECTION_CLOSE_ERROR_MAX_REASONLEN); + if (ccerr->reason == NULL) { + return NGTCP2_ERR_NOMEM; + } + } + + ccerr->reasonlen = + ngtcp2_min(fr->reasonlen, NGTCP2_CONNECTION_CLOSE_ERROR_MAX_REASONLEN); + ngtcp2_cpymem(ccerr->reason, fr->reason, ccerr->reasonlen); + + return 0; +} + +static void conn_recv_path_challenge(ngtcp2_conn *conn, const ngtcp2_path *path, + ngtcp2_path_challenge *fr) { + ngtcp2_path_challenge_entry *ent; + + /* client only responds to PATH_CHALLENGE from the current path or + path which client is migrating to. */ + if (!conn->server && !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) && + (!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path))) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "discard PATH_CHALLENGE from the path which is not current " + "or endpoint is migrating to"); + return; + } + + ent = ngtcp2_ringbuf_push_front(&conn->rx.path_challenge.rb); + ngtcp2_path_challenge_entry_init(ent, path, fr->data); +} + +/* + * conn_reset_congestion_state resets congestion state. + */ +static void conn_reset_congestion_state(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + conn_reset_conn_stat_cc(conn, &conn->cstat); + + conn->cc.reset(&conn->cc, &conn->cstat, ts); + + if (conn->hs_pktns) { + ngtcp2_rtb_reset_cc_state(&conn->hs_pktns->rtb, + conn->hs_pktns->tx.last_pkt_num + 1); + } + ngtcp2_rtb_reset_cc_state(&conn->pktns.rtb, conn->pktns.tx.last_pkt_num + 1); + ngtcp2_rst_init(&conn->rst); + + conn->tx.pacing.next_ts = UINT64_MAX; +} + +static int conn_recv_path_response(ngtcp2_conn *conn, ngtcp2_path_response *fr, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_duration pto, timeout; + ngtcp2_pv *pv = conn->pv, *npv; + uint8_t ent_flags; + + if (!pv) { + return 0; + } + + rv = ngtcp2_pv_validate(pv, &ent_flags, fr->data); + if (rv != 0) { + assert(!ngtcp2_err_is_fatal(rv)); + + return 0; + } + + if (!(pv->flags & NGTCP2_PV_FLAG_DONT_CARE)) { + if (!(pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE)) { + if (pv->dcid.seq != conn->dcid.current.seq) { + assert(conn->dcid.current.cid.datalen); + + rv = conn_retire_dcid(conn, &conn->dcid.current, ts); + if (rv != 0) { + return rv; + } + ngtcp2_dcid_copy(&conn->dcid.current, &pv->dcid); + } + conn_reset_congestion_state(conn, ts); + conn_reset_ecn_validation_state(conn); + } + + if (ngtcp2_path_eq(&pv->dcid.ps.path, &conn->dcid.current.ps.path)) { + conn->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + + if (!conn->local.settings.no_pmtud) { + ngtcp2_conn_stop_pmtud(conn); + + if (!(pv->flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED)) { + rv = conn_start_pmtud(conn); + if (rv != 0) { + return rv; + } + } + } + } + + rv = conn_call_path_validation(conn, pv, + NGTCP2_PATH_VALIDATION_RESULT_SUCCESS); + if (rv != 0) { + return rv; + } + } + + if (pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) { + pto = conn_compute_pto(conn, &conn->pktns); + timeout = 3 * ngtcp2_max(pto, pv->fallback_pto); + + if (ent_flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED) { + assert(conn->server); + + /* Validate path again */ + rv = ngtcp2_pv_new(&npv, &pv->dcid, timeout, + NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE | + NGTCP2_PV_FLAG_MTU_PROBE, + &conn->log, conn->mem); + if (rv != 0) { + return rv; + } + + npv->dcid.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + ngtcp2_dcid_copy(&npv->fallback_dcid, &pv->fallback_dcid); + npv->fallback_pto = pv->fallback_pto; + } else { + rv = ngtcp2_pv_new(&npv, &pv->fallback_dcid, timeout, + NGTCP2_PV_FLAG_DONT_CARE, &conn->log, conn->mem); + if (rv != 0) { + return rv; + } + } + + /* Unset the flag bit so that conn_stop_pv does not retire + DCID. */ + pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE; + + rv = conn_stop_pv(conn, ts); + if (rv != 0) { + ngtcp2_pv_del(npv); + return rv; + } + + conn->pv = npv; + + return 0; + } + + return conn_stop_pv(conn, ts); +} + +/* + * pkt_num_bits returns the number of bits available when packet + * number is encoded in |pkt_numlen| bytes. + */ +static size_t pkt_num_bits(size_t pkt_numlen) { + switch (pkt_numlen) { + case 1: + return 8; + case 2: + return 16; + case 3: + return 24; + case 4: + return 32; + default: + ngtcp2_unreachable(); + } +} + +/* + * pktns_pkt_num_is_duplicate returns nonzero if |pkt_num| is + * duplicated packet number. + */ +static int pktns_pkt_num_is_duplicate(ngtcp2_pktns *pktns, int64_t pkt_num) { + return ngtcp2_gaptr_is_pushed(&pktns->rx.pngap, (uint64_t)pkt_num, 1); +} + +/* + * pktns_commit_recv_pkt_num marks packet number |pkt_num| as + * received. + */ +static int pktns_commit_recv_pkt_num(ngtcp2_pktns *pktns, int64_t pkt_num, + int ack_eliciting, ngtcp2_tstamp ts) { + int rv; + + if (ack_eliciting && pktns->rx.max_ack_eliciting_pkt_num + 1 != pkt_num) { + ngtcp2_acktr_immediate_ack(&pktns->acktr); + } + if (pktns->rx.max_pkt_num < pkt_num) { + pktns->rx.max_pkt_num = pkt_num; + pktns->rx.max_pkt_ts = ts; + } + if (ack_eliciting && pktns->rx.max_ack_eliciting_pkt_num < pkt_num) { + pktns->rx.max_ack_eliciting_pkt_num = pkt_num; + } + + rv = ngtcp2_gaptr_push(&pktns->rx.pngap, (uint64_t)pkt_num, 1); + if (rv != 0) { + return rv; + } + + if (ngtcp2_ksl_len(&pktns->rx.pngap.gap) > 256) { + ngtcp2_gaptr_drop_first_gap(&pktns->rx.pngap); + } + + return 0; +} + +/* + * verify_token verifies |hd| contains |token| in its token field. It + * returns 0 if it succeeds, or NGTCP2_ERR_PROTO. + */ +static int verify_token(const ngtcp2_vec *token, const ngtcp2_pkt_hd *hd) { + if (token->len == hd->token.len && + ngtcp2_cmemeq(token->base, hd->token.base, token->len)) { + return 0; + } + return NGTCP2_ERR_PROTO; +} + +static void pktns_increase_ecn_counts(ngtcp2_pktns *pktns, + const ngtcp2_pkt_info *pi) { + switch (pi->ecn & NGTCP2_ECN_MASK) { + case NGTCP2_ECN_ECT_0: + ++pktns->rx.ecn.ect0; + break; + case NGTCP2_ECN_ECT_1: + ++pktns->rx.ecn.ect1; + break; + case NGTCP2_ECN_CE: + ++pktns->rx.ecn.ce; + break; + } +} + +/* + * vneg_other_versions_includes returns nonzero if |other_versions| of + * length |other_versionslen| includes |version|. |other_versions| is + * the wire image of other_versions field of version_information + * transport parameter, and each version is encoded in network byte + * order. + */ +static int vneg_other_versions_includes(const uint8_t *other_versions, + size_t other_versionslen, + uint32_t version) { + size_t i; + uint32_t v; + + assert(!(other_versionslen & 0x3)); + + if (other_versionslen == 0) { + return 0; + } + + for (i = 0; i < other_versionslen; i += sizeof(uint32_t)) { + other_versions = ngtcp2_get_uint32(&v, other_versions); + + if (version == v) { + return 1; + } + } + + return 0; +} + +static int conn_recv_crypto(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, + ngtcp2_strm *strm, const ngtcp2_crypto *fr); + +static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, + ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts); + +static int conn_process_buffered_protected_pkt(ngtcp2_conn *conn, + ngtcp2_pktns *pktns, + ngtcp2_tstamp ts); + +/* + * conn_recv_handshake_pkt processes received packet |pkt| whose + * length is |pktlen| during handshake period. The buffer pointed by + * |pkt| might contain multiple packets. This function only processes + * one packet. |pkt_ts| is the timestamp when packet is received. + * |ts| should be the current time. Usually they are the same, but + * for buffered packets, |pkt_ts| would be earlier than |ts|. + * + * This function returns the number of bytes it reads if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_RECV_VERSION_NEGOTIATION + * Version Negotiation packet is received. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_DISCARD_PKT + * Packet was discarded because plain text header was malformed; + * or its payload could not be decrypted. + * NGTCP2_ERR_FRAME_FORMAT + * Frame is badly formatted + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed. + * NGTCP2_ERR_CRYPTO + * TLS stack reported error. + * NGTCP2_ERR_PROTO + * Generic QUIC protocol error. + * + * In addition to the above error codes, error codes returned from + * conn_recv_pkt are also returned. + */ +static ngtcp2_ssize +conn_recv_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, ngtcp2_tstamp pkt_ts, + ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + ngtcp2_pkt_hd hd; + ngtcp2_max_frame mfr; + ngtcp2_frame *fr = &mfr.fr; + int rv; + int require_ack = 0; + size_t hdpktlen; + const uint8_t *payload; + size_t payloadlen; + ngtcp2_ssize nwrite; + ngtcp2_crypto_aead *aead; + ngtcp2_crypto_cipher *hp; + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx *hp_ctx; + ngtcp2_hp_mask hp_mask; + ngtcp2_decrypt decrypt; + ngtcp2_pktns *pktns; + ngtcp2_strm *crypto; + ngtcp2_crypto_level crypto_level; + int invalid_reserved_bits = 0; + + if (pktlen == 0) { + return 0; + } + + if (!(pkt[0] & NGTCP2_HEADER_FORM_BIT)) { + if (conn->state == NGTCP2_CS_SERVER_INITIAL) { + /* Ignore 1RTT packet unless server's first Handshake packet has + been transmitted. */ + return (ngtcp2_ssize)pktlen; + } + + if (conn->pktns.crypto.rx.ckm) { + return 0; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "buffering 1RTT packet len=%zu", pktlen); + + rv = conn_buffer_pkt(conn, &conn->pktns, path, pi, pkt, pktlen, dgramlen, + ts); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + return (ngtcp2_ssize)pktlen; + } + + nread = ngtcp2_pkt_decode_hd_long(&hd, pkt, pktlen); + if (nread < 0) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (hd.type == NGTCP2_PKT_VERSION_NEGOTIATION) { + hdpktlen = (size_t)nread; + + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + + if (conn->server) { + return NGTCP2_ERR_DISCARD_PKT; + } + + /* Receiving Version Negotiation packet after getting Handshake + packet from server is invalid. */ + if (conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!ngtcp2_cid_eq(&conn->oscid, &hd.dcid)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!ngtcp2_cid_eq(&conn->dcid.current.cid, &hd.scid)) { + /* Just discard invalid Version Negotiation packet */ + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched SCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + rv = conn_on_version_negotiation(conn, &hd, pkt + hdpktlen, + pktlen - hdpktlen); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + return NGTCP2_ERR_DISCARD_PKT; + } + return NGTCP2_ERR_RECV_VERSION_NEGOTIATION; + } else if (hd.type == NGTCP2_PKT_RETRY) { + hdpktlen = (size_t)nread; + + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + + if (conn->server) { + return NGTCP2_ERR_DISCARD_PKT; + } + + /* Receiving Retry packet after getting Initial packet from server + is invalid. */ + if (conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn->client_chosen_version != hd.version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + rv = conn_on_retry(conn, &hd, hdpktlen, pkt, pktlen, ts); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + return NGTCP2_ERR_DISCARD_PKT; + } + return (ngtcp2_ssize)pktlen; + } + + if (pktlen < (size_t)nread + hd.len) { + return NGTCP2_ERR_DISCARD_PKT; + } + + pktlen = (size_t)nread + hd.len; + + if (!ngtcp2_is_supported_version(hd.version)) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn->server) { + if (hd.version != conn->client_chosen_version && + (!conn->negotiated_version || hd.version != conn->negotiated_version)) { + return NGTCP2_ERR_DISCARD_PKT; + } + } else if (hd.version != conn->client_chosen_version && + conn->negotiated_version && + hd.version != conn->negotiated_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + /* Quoted from spec: if subsequent packets of those types include a + different Source Connection ID, they MUST be discarded. */ + if ((conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) && + !ngtcp2_cid_eq(&conn->dcid.current.cid, &hd.scid)) { + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched SCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + + switch (hd.type) { + case NGTCP2_PKT_0RTT: + if (!conn->server) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (hd.version != conn->client_chosen_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) { + if (conn->early.ckm) { + ngtcp2_ssize nread2; + /* TODO Avoid to parse header twice. */ + nread2 = + conn_recv_pkt(conn, path, pi, pkt, pktlen, dgramlen, pkt_ts, ts); + if (nread2 < 0) { + return nread2; + } + } + + /* Discard 0-RTT packet if we don't have a key to decrypt it. */ + return (ngtcp2_ssize)pktlen; + } + + /* Buffer re-ordered 0-RTT packet. */ + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "buffering 0-RTT packet len=%zu", pktlen); + + rv = conn_buffer_pkt(conn, conn->in_pktns, path, pi, pkt, pktlen, dgramlen, + ts); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + return (ngtcp2_ssize)pktlen; + case NGTCP2_PKT_INITIAL: + if (!conn->in_pktns) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "Initial packet is discarded because keys have been discarded"); + return (ngtcp2_ssize)pktlen; + } + + assert(conn->in_pktns); + + if (conn->server) { + if (dgramlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "Initial packet was ignored because it is included in UDP datagram " + "less than %zu bytes: %zu bytes", + NGTCP2_MAX_UDP_PAYLOAD_SIZE, dgramlen); + return NGTCP2_ERR_DISCARD_PKT; + } + if (conn->local.settings.token.len) { + rv = verify_token(&conn->local.settings.token, &hd); + if (rv != 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because token is invalid"); + return NGTCP2_ERR_DISCARD_PKT; + } + } + if ((conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) == 0) { + /* Set rcid here so that it is available to callback. If this + packet is discarded later in this function and no packet is + processed in this connection attempt so far, connection + will be dropped. */ + conn->rcid = hd.dcid; + + rv = conn_call_recv_client_initial(conn, &hd.dcid); + if (rv != 0) { + return rv; + } + } + } else { + if (hd.token.len != 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because token is not empty"); + return NGTCP2_ERR_DISCARD_PKT; + } + + if (hd.version != conn->client_chosen_version && + !conn->negotiated_version && conn->vneg.version != hd.version) { + if (!vneg_other_versions_includes(conn->vneg.other_versions, + conn->vneg.other_versionslen, + hd.version)) { + return NGTCP2_ERR_DISCARD_PKT; + } + + /* Install new Initial keys using QUIC version = hd.version */ + rv = conn_call_version_negotiation( + conn, hd.version, + (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) + ? &conn->dcid.current.cid + : &conn->rcid); + if (rv != 0) { + return rv; + } + + assert(conn->vneg.version == hd.version); + } + } + + pktns = conn->in_pktns; + crypto = &pktns->crypto.strm; + crypto_level = NGTCP2_CRYPTO_LEVEL_INITIAL; + + if (hd.version == conn->client_chosen_version) { + ckm = pktns->crypto.rx.ckm; + hp_ctx = &pktns->crypto.rx.hp_ctx; + } else { + assert(conn->vneg.version == hd.version); + + ckm = conn->vneg.rx.ckm; + hp_ctx = &conn->vneg.rx.hp_ctx; + } + + break; + case NGTCP2_PKT_HANDSHAKE: + if (hd.version != conn->negotiated_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!conn->hs_pktns->crypto.rx.ckm) { + if (conn->server) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PKT, + "Handshake packet at this point is unexpected and discarded"); + return (ngtcp2_ssize)pktlen; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "buffering Handshake packet len=%zu", pktlen); + + rv = conn_buffer_pkt(conn, conn->hs_pktns, path, pi, pkt, pktlen, + dgramlen, ts); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + return (ngtcp2_ssize)pktlen; + } + + pktns = conn->hs_pktns; + crypto = &pktns->crypto.strm; + crypto_level = NGTCP2_CRYPTO_LEVEL_HANDSHAKE; + ckm = pktns->crypto.rx.ckm; + hp_ctx = &pktns->crypto.rx.hp_ctx; + + break; + default: + /* unknown packet type */ + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of unknown packet type"); + return (ngtcp2_ssize)pktlen; + } + + hp_mask = conn->callbacks.hp_mask; + decrypt = conn->callbacks.decrypt; + aead = &pktns->crypto.ctx.aead; + hp = &pktns->crypto.ctx.hp; + + assert(ckm); + assert(hp_mask); + assert(decrypt); + + rv = conn_ensure_decrypt_hp_buffer(conn, (size_t)nread + 4); + if (rv != 0) { + return rv; + } + + nwrite = decrypt_hp(&hd, conn->crypto.decrypt_hp_buf.base, hp, pkt, pktlen, + (size_t)nread, hp_ctx, hp_mask); + if (nwrite < 0) { + if (ngtcp2_err_is_fatal((int)nwrite)) { + return nwrite; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet number"); + return NGTCP2_ERR_DISCARD_PKT; + } + + hdpktlen = (size_t)nwrite; + payload = pkt + hdpktlen; + payloadlen = hd.len - hd.pkt_numlen; + + hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->rx.max_pkt_num, hd.pkt_num, + pkt_num_bits(hd.pkt_numlen)); + if (hd.pkt_num > NGTCP2_MAX_PKT_NUM) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num); + return NGTCP2_ERR_DISCARD_PKT; + } + + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + + rv = ngtcp2_pkt_verify_reserved_bits(conn->crypto.decrypt_hp_buf.base[0]); + if (rv != 0) { + invalid_reserved_bits = 1; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet has incorrect reserved bits"); + + /* Will return error after decrypting payload */ + } + + if (pktns_pkt_num_is_duplicate(pktns, hd.pkt_num)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was discarded because of duplicated packet number"); + return NGTCP2_ERR_DISCARD_PKT; + } + + rv = conn_ensure_decrypt_buffer(conn, payloadlen); + if (rv != 0) { + return rv; + } + + nwrite = decrypt_pkt(conn->crypto.decrypt_buf.base, aead, payload, payloadlen, + conn->crypto.decrypt_hp_buf.base, hdpktlen, hd.pkt_num, + ckm, decrypt); + if (nwrite < 0) { + if (ngtcp2_err_is_fatal((int)nwrite)) { + return nwrite; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet payload"); + return NGTCP2_ERR_DISCARD_PKT; + } + + if (invalid_reserved_bits) { + return NGTCP2_ERR_PROTO; + } + + if (!conn->server && hd.version != conn->client_chosen_version && + !conn->negotiated_version) { + conn->negotiated_version = hd.version; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "the negotiated version is 0x%08x", + conn->negotiated_version); + } + + payload = conn->crypto.decrypt_buf.base; + payloadlen = (size_t)nwrite; + + switch (hd.type) { + case NGTCP2_PKT_INITIAL: + if (!conn->server || ((conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED) && + !ngtcp2_cid_eq(&conn->rcid, &hd.dcid))) { + rv = conn_verify_dcid(conn, NULL, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + } + break; + case NGTCP2_PKT_HANDSHAKE: + rv = conn_verify_dcid(conn, NULL, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + break; + default: + ngtcp2_unreachable(); + } + + if (payloadlen == 0) { + /* QUIC packet must contain at least one frame */ + if (hd.type == NGTCP2_PKT_INITIAL) { + return NGTCP2_ERR_DISCARD_PKT; + } + return NGTCP2_ERR_PROTO; + } + + if (hd.type == NGTCP2_PKT_INITIAL && + !(conn->flags & NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED)) { + conn->flags |= NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED; + if (!conn->server) { + conn->dcid.current.cid = hd.scid; + } + } + + ngtcp2_qlog_pkt_received_start(&conn->qlog); + + for (; payloadlen;) { + nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen); + if (nread < 0) { + return nread; + } + + payload += nread; + payloadlen -= (size_t)nread; + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + fr->ack.ack_delay = 0; + fr->ack.ack_delay_unscaled = 0; + break; + } + + ngtcp2_log_rx_fr(&conn->log, &hd, fr); + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if (!conn->server && hd.type == NGTCP2_PKT_HANDSHAKE) { + conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + } + rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PADDING: + break; + case NGTCP2_FRAME_CRYPTO: + if (!conn->server && !conn->negotiated_version && + ngtcp2_vec_len(fr->crypto.data, fr->crypto.datacnt)) { + conn->negotiated_version = hd.version; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "the negotiated version is 0x%08x", + conn->negotiated_version); + } + + rv = conn_recv_crypto(conn, crypto_level, crypto, &fr->crypto); + if (rv != 0) { + return rv; + } + require_ack = 1; + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + rv = conn_recv_connection_close(conn, &fr->connection_close); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PING: + require_ack = 1; + break; + default: + return NGTCP2_ERR_PROTO; + } + + ngtcp2_qlog_write_frame(&conn->qlog, fr); + } + + if (hd.type == NGTCP2_PKT_HANDSHAKE) { + /* Successful processing of Handshake packet from a remote + endpoint validates its source address. */ + conn->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + } + + ngtcp2_qlog_pkt_received_end(&conn->qlog, &hd, pktlen); + + rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, require_ack, pkt_ts); + if (rv != 0) { + return rv; + } + + pktns_increase_ecn_counts(pktns, pi); + + /* TODO Initial and Handshake are always acknowledged without + delay. */ + if (require_ack && + (++pktns->acktr.rx_npkt >= conn->local.settings.ack_thresh || + (pi->ecn & NGTCP2_ECN_MASK) == NGTCP2_ECN_CE)) { + ngtcp2_acktr_immediate_ack(&pktns->acktr); + } + + rv = ngtcp2_conn_sched_ack(conn, &pktns->acktr, hd.pkt_num, require_ack, + pkt_ts); + if (rv != 0) { + return rv; + } + + conn_restart_timer_on_read(conn, ts); + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + return conn->state == NGTCP2_CS_DRAINING ? NGTCP2_ERR_DRAINING + : (ngtcp2_ssize)pktlen; +} + +static int is_unrecoverable_error(int liberr) { + switch (liberr) { + case NGTCP2_ERR_CRYPTO: + case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + case NGTCP2_ERR_TRANSPORT_PARAM: + case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE: + return 1; + } + + return 0; +} + +/* + * conn_recv_handshake_cpkt processes compound packet during + * handshake. The buffer pointed by |pkt| might contain multiple + * packets. The 1RTT packet must be the last one because it does not + * have payload length field. + * + * This function returns the same error code returned by + * conn_recv_handshake_pkt. + */ +static ngtcp2_ssize conn_recv_handshake_cpkt(ngtcp2_conn *conn, + const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + size_t dgramlen = pktlen; + const uint8_t *origpkt = pkt; + uint32_t version; + + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + conn->dcid.current.bytes_recv += dgramlen; + } + + while (pktlen) { + nread = + conn_recv_handshake_pkt(conn, path, pi, pkt, pktlen, dgramlen, ts, ts); + if (nread < 0) { + if (ngtcp2_err_is_fatal((int)nread)) { + return nread; + } + + if (nread == NGTCP2_ERR_DRAINING) { + return NGTCP2_ERR_DRAINING; + } + + if ((pkt[0] & NGTCP2_HEADER_FORM_BIT) && pktlen > 4) { + /* Not a Version Negotiation packet */ + ngtcp2_get_uint32(&version, &pkt[1]); + if (ngtcp2_pkt_get_type_long(version, pkt[0]) == NGTCP2_PKT_INITIAL) { + if (conn->server) { + if (is_unrecoverable_error((int)nread)) { + /* If server gets crypto error from TLS stack, it is + unrecoverable, therefore drop connection. */ + return nread; + } + + /* If server discards first Initial, then drop connection + state. This is because SCID in packet might be corrupted + and the current connection state might wrongly discard + valid packet and prevent the handshake from + completing. */ + if (conn->in_pktns && conn->in_pktns->rx.max_pkt_num == -1) { + return NGTCP2_ERR_DROP_CONN; + } + + return (ngtcp2_ssize)dgramlen; + } + /* client */ + if (is_unrecoverable_error((int)nread)) { + /* If client gets crypto error from TLS stack, it is + unrecoverable, therefore drop connection. */ + return nread; + } + return (ngtcp2_ssize)dgramlen; + } + } + + if (nread == NGTCP2_ERR_DISCARD_PKT) { + return (ngtcp2_ssize)dgramlen; + } + + return nread; + } + + if (nread == 0) { + assert(!(pkt[0] & NGTCP2_HEADER_FORM_BIT)); + return pkt - origpkt; + } + + assert(pktlen >= (size_t)nread); + pkt += nread; + pktlen -= (size_t)nread; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "read packet %td left %zu", nread, pktlen); + } + + return (ngtcp2_ssize)dgramlen; +} + +int ngtcp2_conn_init_stream(ngtcp2_conn *conn, ngtcp2_strm *strm, + int64_t stream_id, void *stream_user_data) { + int rv; + uint64_t max_rx_offset; + uint64_t max_tx_offset; + int local_stream = conn_local_stream(conn, stream_id); + + assert(conn->remote.transport_params); + + if (bidi_stream(stream_id)) { + if (local_stream) { + max_rx_offset = + conn->local.transport_params.initial_max_stream_data_bidi_local; + max_tx_offset = + conn->remote.transport_params->initial_max_stream_data_bidi_remote; + } else { + max_rx_offset = + conn->local.transport_params.initial_max_stream_data_bidi_remote; + max_tx_offset = + conn->remote.transport_params->initial_max_stream_data_bidi_local; + } + } else if (local_stream) { + max_rx_offset = 0; + max_tx_offset = conn->remote.transport_params->initial_max_stream_data_uni; + } else { + max_rx_offset = conn->local.transport_params.initial_max_stream_data_uni; + max_tx_offset = 0; + } + + ngtcp2_strm_init(strm, stream_id, NGTCP2_STRM_FLAG_NONE, max_rx_offset, + max_tx_offset, stream_user_data, &conn->frc_objalloc, + conn->mem); + + rv = ngtcp2_map_insert(&conn->strms, (ngtcp2_map_key_type)strm->stream_id, + strm); + if (rv != 0) { + assert(rv != NGTCP2_ERR_INVALID_ARGUMENT); + goto fail; + } + + return 0; + +fail: + ngtcp2_strm_free(strm); + return rv; +} + +/* + * conn_emit_pending_stream_data passes buffered ordered stream data + * to the application. |rx_offset| is the first offset to deliver to + * the application. This function assumes that the data up to + * |rx_offset| has been delivered already. This function only passes + * the ordered data without any gap. If there is a gap, it stops + * providing the data to the application, and returns. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_emit_pending_stream_data(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t rx_offset) { + size_t datalen; + const uint8_t *data; + int rv; + uint64_t offset; + uint32_t sdflags; + int handshake_completed = conn_is_handshake_completed(conn); + + if (!strm->rx.rob) { + return 0; + } + + for (;;) { + /* Stop calling callback if application has called + ngtcp2_conn_shutdown_stream_read() inside the callback. + Because it doubly counts connection window. */ + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { + return 0; + } + + datalen = ngtcp2_rob_data_at(strm->rx.rob, &data, rx_offset); + if (datalen == 0) { + assert(rx_offset == ngtcp2_strm_rx_offset(strm)); + return 0; + } + + offset = rx_offset; + rx_offset += datalen; + + sdflags = NGTCP2_STREAM_DATA_FLAG_NONE; + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + rx_offset == strm->rx.last_offset) { + sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN; + } + if (!handshake_completed) { + sdflags |= NGTCP2_STREAM_DATA_FLAG_EARLY; + } + + rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, datalen); + if (rv != 0) { + return rv; + } + + ngtcp2_rob_pop(strm->rx.rob, rx_offset - datalen, datalen); + } +} + +/* + * conn_recv_crypto is called when CRYPTO frame |fr| is received. + * |rx_offset_base| is the offset in the entire TLS handshake stream. + * fr->offset specifies the offset in each encryption level. + * |max_rx_offset| is, if it is nonzero, the maximum offset in the + * entire TLS handshake stream that |fr| can carry. |crypto_level| is + * the encryption level where this data is received. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * CRYPTO frame has invalid offset. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CRYPTO + * TLS stack reported error. + * NGTCP2_ERR_FRAME_ENCODING + * The end offset exceeds the maximum value. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_recv_crypto(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, + ngtcp2_strm *crypto, const ngtcp2_crypto *fr) { + uint64_t fr_end_offset; + uint64_t rx_offset; + int rv; + + if (fr->datacnt == 0) { + return 0; + } + + fr_end_offset = fr->offset + fr->data[0].len; + + if (NGTCP2_MAX_VARINT < fr_end_offset) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + rx_offset = ngtcp2_strm_rx_offset(crypto); + + if (fr_end_offset <= rx_offset) { + if (conn->server && + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT) && + crypto_level == NGTCP2_CRYPTO_LEVEL_INITIAL) { + /* recovery draft: Speeding Up Handshake Completion + + When a server receives an Initial packet containing duplicate + CRYPTO data, it can assume the client did not receive all of + the server's CRYPTO data sent in Initial packets, or the + client's estimated RTT is too small. ... To speed up + handshake completion under these conditions, an endpoint MAY + send a packet containing unacknowledged CRYPTO data earlier + than the PTO expiry, subject to address validation limits; + ... */ + conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT; + conn->in_pktns->rtb.probe_pkt_left = 1; + conn->hs_pktns->rtb.probe_pkt_left = 1; + } + return 0; + } + + crypto->rx.last_offset = ngtcp2_max(crypto->rx.last_offset, fr_end_offset); + + /* TODO Before dispatching incoming data to TLS stack, make sure + that previous data in previous encryption level has been + completely sent to TLS stack. Usually, if data is left, it is an + error because key is generated after consuming all data in the + previous encryption level. */ + if (fr->offset <= rx_offset) { + size_t ncut = (size_t)(rx_offset - fr->offset); + const uint8_t *data = fr->data[0].base + ncut; + size_t datalen = fr->data[0].len - ncut; + uint64_t offset = rx_offset; + + rx_offset += datalen; + rv = ngtcp2_strm_update_rx_offset(crypto, rx_offset); + if (rv != 0) { + return rv; + } + + rv = conn_call_recv_crypto_data(conn, crypto_level, offset, data, datalen); + if (rv != 0) { + return rv; + } + + rv = conn_emit_pending_crypto_data(conn, crypto_level, crypto, rx_offset); + if (rv != 0) { + return rv; + } + + return 0; + } + + if (fr_end_offset - rx_offset > NGTCP2_MAX_REORDERED_CRYPTO_DATA) { + return NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED; + } + + return ngtcp2_strm_recv_reordering(crypto, fr->data[0].base, fr->data[0].len, + fr->offset); +} + +/* + * conn_max_data_violated returns nonzero if receiving |datalen| + * violates connection flow control on local endpoint. + */ +static int conn_max_data_violated(ngtcp2_conn *conn, uint64_t datalen) { + return conn->rx.max_offset - conn->rx.offset < datalen; +} + +/* + * conn_recv_stream is called when STREAM frame |fr| is received. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_STREAM_STATE + * STREAM frame is received for a local stream which is not + * initiated; or STREAM frame is received for a local + * unidirectional stream + * NGTCP2_ERR_STREAM_LIMIT + * STREAM frame has remote stream ID which is strictly greater + * than the allowed limit. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_FLOW_CONTROL + * Flow control limit is violated; or the end offset of stream + * data is beyond the NGTCP2_MAX_VARINT. + * NGTCP2_ERR_FINAL_SIZE + * STREAM frame has strictly larger end offset than it is + * permitted. + */ +static int conn_recv_stream(ngtcp2_conn *conn, const ngtcp2_stream *fr) { + int rv; + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; + uint64_t rx_offset, fr_end_offset; + int local_stream; + int bidi; + uint64_t datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + uint32_t sdflags = NGTCP2_STREAM_DATA_FLAG_NONE; + + local_stream = conn_local_stream(conn, fr->stream_id); + bidi = bidi_stream(fr->stream_id); + + if (bidi) { + if (local_stream) { + if (conn->local.bidi.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + } else if (conn->remote.bidi.max_streams < + ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.bidi.idtr; + } else { + if (local_stream) { + return NGTCP2_ERR_STREAM_STATE; + } + if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.uni.idtr; + } + + if (NGTCP2_MAX_VARINT - datalen < fr->offset) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { + /* TODO The stream has been closed. This should be responded + with RESET_STREAM, or simply ignored. */ + return 0; + } + + rv = ngtcp2_idtr_open(idtr, fr->stream_id); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); + /* TODO The stream has been closed. This should be responded + with RESET_STREAM, or simply ignored. */ + return 0; + } + + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + /* TODO Perhaps, call new_stream callback? */ + rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + + rv = conn_call_stream_open(conn, strm); + if (rv != 0) { + return rv; + } + + if (!bidi) { + ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_WR); + } + } + + fr_end_offset = fr->offset + datalen; + + if (strm->rx.max_offset < fr_end_offset) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + if (strm->rx.last_offset < fr_end_offset) { + uint64_t len = fr_end_offset - strm->rx.last_offset; + + if (conn_max_data_violated(conn, len)) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + conn->rx.offset += len; + + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { + ngtcp2_conn_extend_max_offset(conn, len); + } + } + + rx_offset = ngtcp2_strm_rx_offset(strm); + + if (fr->fin) { + if (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) { + if (strm->rx.last_offset != fr_end_offset) { + return NGTCP2_ERR_FINAL_SIZE; + } + + if (strm->flags & NGTCP2_STRM_FLAG_RECV_RST) { + return 0; + } + + if (rx_offset == fr_end_offset) { + return 0; + } + } else if (strm->rx.last_offset > fr_end_offset) { + return NGTCP2_ERR_FINAL_SIZE; + } else { + strm->rx.last_offset = fr_end_offset; + + ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_RD); + } + } else { + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + strm->rx.last_offset < fr_end_offset) { + return NGTCP2_ERR_FINAL_SIZE; + } + + strm->rx.last_offset = ngtcp2_max(strm->rx.last_offset, fr_end_offset); + + if (fr_end_offset <= rx_offset) { + return 0; + } + + if (strm->flags & NGTCP2_STRM_FLAG_RECV_RST) { + return 0; + } + } + + if (fr->offset <= rx_offset) { + size_t ncut = (size_t)(rx_offset - fr->offset); + uint64_t offset = rx_offset; + const uint8_t *data; + int fin; + + if (fr->datacnt) { + data = fr->data[0].base + ncut; + datalen -= ncut; + + rx_offset += datalen; + rv = ngtcp2_strm_update_rx_offset(strm, rx_offset); + if (rv != 0) { + return rv; + } + } else { + data = NULL; + datalen = 0; + } + + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { + return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); + } + + fin = (strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + rx_offset == strm->rx.last_offset; + + if (fin || datalen) { + if (fin) { + sdflags |= NGTCP2_STREAM_DATA_FLAG_FIN; + } + if (!conn_is_handshake_completed(conn)) { + sdflags |= NGTCP2_STREAM_DATA_FLAG_EARLY; + } + rv = conn_call_recv_stream_data(conn, strm, sdflags, offset, data, + (size_t)datalen); + if (rv != 0) { + return rv; + } + + rv = conn_emit_pending_stream_data(conn, strm, rx_offset); + if (rv != 0) { + return rv; + } + } + } else if (fr->datacnt) { + rv = ngtcp2_strm_recv_reordering(strm, fr->data[0].base, fr->data[0].len, + fr->offset); + if (rv != 0) { + return rv; + } + } + return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); +} + +/* + * conn_reset_stream adds RESET_STREAM frame to the transmission + * queue. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_reset_stream(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t app_error_code) { + int rv; + ngtcp2_frame_chain *frc; + ngtcp2_pktns *pktns = &conn->pktns; + + rv = ngtcp2_frame_chain_objalloc_new(&frc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + frc->fr.type = NGTCP2_FRAME_RESET_STREAM; + frc->fr.reset_stream.stream_id = strm->stream_id; + frc->fr.reset_stream.app_error_code = app_error_code; + frc->fr.reset_stream.final_size = strm->tx.offset; + + /* TODO This prepends RESET_STREAM to pktns->tx.frq. */ + frc->next = pktns->tx.frq; + pktns->tx.frq = frc; + + return 0; +} + +/* + * conn_stop_sending adds STOP_SENDING frame to the transmission + * queue. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_stop_sending(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t app_error_code) { + int rv; + ngtcp2_frame_chain *frc; + ngtcp2_pktns *pktns = &conn->pktns; + + rv = ngtcp2_frame_chain_objalloc_new(&frc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + frc->fr.type = NGTCP2_FRAME_STOP_SENDING; + frc->fr.stop_sending.stream_id = strm->stream_id; + frc->fr.stop_sending.app_error_code = app_error_code; + + /* TODO This prepends STOP_SENDING to pktns->tx.frq. */ + frc->next = pktns->tx.frq; + pktns->tx.frq = frc; + + return 0; +} + +/* + * handle_max_remote_streams_extension extends + * |*punsent_max_remote_streams| by |n| if a condition allows it. + */ +static void +handle_max_remote_streams_extension(uint64_t *punsent_max_remote_streams, + size_t n) { + if ( +#if SIZE_MAX > UINT32_MAX + NGTCP2_MAX_STREAMS < n || +#endif /* SIZE_MAX > UINT32_MAX */ + *punsent_max_remote_streams > (uint64_t)(NGTCP2_MAX_STREAMS - n)) { + *punsent_max_remote_streams = NGTCP2_MAX_STREAMS; + } else { + *punsent_max_remote_streams += n; + } +} + +/* + * conn_recv_reset_stream is called when RESET_STREAM |fr| is + * received. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_STREAM_STATE + * RESET_STREAM frame is received to the local stream which is not + * initiated. + * NGTCP2_ERR_STREAM_LIMIT + * RESET_STREAM frame has remote stream ID which is strictly + * greater than the allowed limit. + * NGTCP2_ERR_PROTO + * RESET_STREAM frame is received to the local unidirectional + * stream + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_FLOW_CONTROL + * Flow control limit is violated; or the final size is beyond the + * NGTCP2_MAX_VARINT. + * NGTCP2_ERR_FINAL_SIZE + * The final offset is strictly larger than it is permitted. + */ +static int conn_recv_reset_stream(ngtcp2_conn *conn, + const ngtcp2_reset_stream *fr) { + ngtcp2_strm *strm; + int local_stream = conn_local_stream(conn, fr->stream_id); + int bidi = bidi_stream(fr->stream_id); + uint64_t datalen; + ngtcp2_idtr *idtr; + int rv; + + /* TODO share this piece of code */ + if (bidi) { + if (local_stream) { + if (conn->local.bidi.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + } else if (conn->remote.bidi.max_streams < + ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.bidi.idtr; + } else { + if (local_stream) { + return NGTCP2_ERR_PROTO; + } + if (conn->remote.uni.max_streams < ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.uni.idtr; + } + + if (NGTCP2_MAX_VARINT < fr->final_size) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { + return 0; + } + + rv = ngtcp2_idtr_open(idtr, fr->stream_id); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); + return 0; + } + + if (conn_initial_stream_rx_offset(conn, fr->stream_id) < fr->final_size || + conn_max_data_violated(conn, fr->final_size)) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + /* Stream is reset before we create ngtcp2_strm object. */ + conn->rx.offset += fr->final_size; + ngtcp2_conn_extend_max_offset(conn, fr->final_size); + + rv = conn_call_stream_reset(conn, fr->stream_id, fr->final_size, + fr->app_error_code, NULL); + if (rv != 0) { + return rv; + } + + /* There will be no activity in this stream because we got + RESET_STREAM and don't write stream data any further. This + effectively allows another new stream for peer. */ + if (bidi) { + handle_max_remote_streams_extension(&conn->remote.bidi.unsent_max_streams, + 1); + } else { + handle_max_remote_streams_extension(&conn->remote.uni.unsent_max_streams, + 1); + } + + return 0; + } + + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD)) { + if (strm->rx.last_offset != fr->final_size) { + return NGTCP2_ERR_FINAL_SIZE; + } + } else if (strm->rx.last_offset > fr->final_size) { + return NGTCP2_ERR_FINAL_SIZE; + } + + if (strm->flags & NGTCP2_STRM_FLAG_RECV_RST) { + return 0; + } + + if (strm->rx.max_offset < fr->final_size) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + datalen = fr->final_size - strm->rx.last_offset; + + if (conn_max_data_violated(conn, datalen)) { + return NGTCP2_ERR_FLOW_CONTROL; + } + + rv = conn_call_stream_reset(conn, fr->stream_id, fr->final_size, + fr->app_error_code, strm->stream_user_data); + if (rv != 0) { + return rv; + } + + /* Extend connection flow control window for the amount of data + which are not passed to application. */ + if (!(strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING)) { + ngtcp2_conn_extend_max_offset(conn, strm->rx.last_offset - + ngtcp2_strm_rx_offset(strm)); + } + + conn->rx.offset += datalen; + ngtcp2_conn_extend_max_offset(conn, datalen); + + strm->rx.last_offset = fr->final_size; + strm->flags |= NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_RECV_RST; + + ngtcp2_strm_set_app_error_code(strm, fr->app_error_code); + + return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); +} + +/* + * conn_recv_stop_sending is called when STOP_SENDING |fr| is received. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_STREAM_STATE + * STOP_SENDING frame is received for a local stream which is not + * initiated; or STOP_SENDING frame is received for a local + * unidirectional stream. + * NGTCP2_ERR_STREAM_LIMIT + * STOP_SENDING frame has remote stream ID which is strictly + * greater than the allowed limit. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_recv_stop_sending(ngtcp2_conn *conn, + const ngtcp2_stop_sending *fr) { + int rv; + ngtcp2_strm *strm; + ngtcp2_idtr *idtr; + int local_stream = conn_local_stream(conn, fr->stream_id); + int bidi = bidi_stream(fr->stream_id); + + if (bidi) { + if (local_stream) { + if (conn->local.bidi.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + } else if (conn->remote.bidi.max_streams < + ngtcp2_ord_stream_id(fr->stream_id)) { + return NGTCP2_ERR_STREAM_LIMIT; + } + + idtr = &conn->remote.bidi.idtr; + } else { + if (!local_stream || conn->local.uni.next_stream_id <= fr->stream_id) { + return NGTCP2_ERR_STREAM_STATE; + } + + idtr = &conn->remote.uni.idtr; + } + + strm = ngtcp2_conn_find_stream(conn, fr->stream_id); + if (strm == NULL) { + if (local_stream) { + return 0; + } + rv = ngtcp2_idtr_open(idtr, fr->stream_id); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + assert(rv == NGTCP2_ERR_STREAM_IN_USE); + return 0; + } + + /* Frame is received reset before we create ngtcp2_strm + object. */ + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + rv = ngtcp2_conn_init_stream(conn, strm, fr->stream_id, NULL); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + + rv = conn_call_stream_open(conn, strm); + if (rv != 0) { + return rv; + } + } + + ngtcp2_strm_set_app_error_code(strm, fr->app_error_code); + + /* No RESET_STREAM is required if we have sent FIN and all data have + been acknowledged. */ + if (!ngtcp2_strm_is_all_tx_data_fin_acked(strm) && + !(strm->flags & NGTCP2_STRM_FLAG_SENT_RST)) { + rv = conn_reset_stream(conn, strm, fr->app_error_code); + if (rv != 0) { + return rv; + } + } + + strm->flags |= NGTCP2_STRM_FLAG_SHUT_WR | NGTCP2_STRM_FLAG_SENT_RST; + + if (ngtcp2_strm_is_tx_queued(strm) && !ngtcp2_strm_streamfrq_empty(strm)) { + assert(conn->tx.strmq_nretrans); + --conn->tx.strmq_nretrans; + } + + ngtcp2_strm_streamfrq_clear(strm); + + return ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); +} + +/* + * check_stateless_reset returns nonzero if Stateless Reset |sr| + * coming via |path| is valid against |dcid|. + */ +static int check_stateless_reset(const ngtcp2_dcid *dcid, + const ngtcp2_path *path, + const ngtcp2_pkt_stateless_reset *sr) { + return ngtcp2_path_eq(&dcid->ps.path, path) && + ngtcp2_dcid_verify_stateless_reset_token( + dcid, sr->stateless_reset_token) == 0; +} + +/* + * conn_on_stateless_reset decodes Stateless Reset from the buffer + * pointed by |payload| whose length is |payloadlen|. |payload| + * should start after first byte of packet. + * + * If Stateless Reset is decoded, and the Stateless Reset Token is + * validated, the connection is closed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Could not decode Stateless Reset; or Stateless Reset Token does + * not match; or No stateless reset token is available. + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + */ +static int conn_on_stateless_reset(ngtcp2_conn *conn, const ngtcp2_path *path, + const uint8_t *payload, size_t payloadlen) { + int rv = 1; + ngtcp2_pv *pv = conn->pv; + ngtcp2_dcid *dcid; + ngtcp2_pkt_stateless_reset sr; + size_t len, i; + + rv = ngtcp2_pkt_decode_stateless_reset(&sr, payload, payloadlen); + if (rv != 0) { + return rv; + } + + if (!check_stateless_reset(&conn->dcid.current, path, &sr) && + (!pv || (!check_stateless_reset(&pv->dcid, path, &sr) && + (!(pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) || + !check_stateless_reset(&pv->fallback_dcid, path, &sr))))) { + len = ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, i); + if (check_stateless_reset(dcid, path, &sr)) { + break; + } + } + + if (i == len) { + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + if (check_stateless_reset(dcid, path, &sr)) { + break; + } + } + + if (i == len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + } + } + + conn->state = NGTCP2_CS_DRAINING; + + ngtcp2_log_rx_sr(&conn->log, &sr); + + ngtcp2_qlog_stateless_reset_pkt_received(&conn->qlog, &sr); + + return conn_call_recv_stateless_reset(conn, &sr); +} + +/* + * conn_recv_max_streams processes the incoming MAX_STREAMS frame + * |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + * NGTCP2_ERR_FRAME_ENCODING + * The maximum streams field exceeds the maximum value. + */ +static int conn_recv_max_streams(ngtcp2_conn *conn, + const ngtcp2_max_streams *fr) { + uint64_t n; + + if (fr->max_streams > NGTCP2_MAX_STREAMS) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + n = ngtcp2_min(fr->max_streams, NGTCP2_MAX_STREAMS); + + if (fr->type == NGTCP2_FRAME_MAX_STREAMS_BIDI) { + if (conn->local.bidi.max_streams < n) { + conn->local.bidi.max_streams = n; + return conn_call_extend_max_local_streams_bidi(conn, n); + } + return 0; + } + + if (conn->local.uni.max_streams < n) { + conn->local.uni.max_streams = n; + return conn_call_extend_max_local_streams_uni(conn, n); + } + return 0; +} + +static int conn_retire_dcid_prior_to(ngtcp2_conn *conn, ngtcp2_ringbuf *rb, + uint64_t retire_prior_to) { + size_t i; + ngtcp2_dcid *dcid, *last; + int rv; + + for (i = 0; i < ngtcp2_ringbuf_len(rb);) { + dcid = ngtcp2_ringbuf_get(rb, i); + if (dcid->seq >= retire_prior_to) { + ++i; + continue; + } + + rv = conn_retire_dcid_seq(conn, dcid->seq); + if (rv != 0) { + return rv; + } + + if (i == 0) { + ngtcp2_ringbuf_pop_front(rb); + continue; + } + + if (i == ngtcp2_ringbuf_len(rb) - 1) { + ngtcp2_ringbuf_pop_back(rb); + break; + } + + last = ngtcp2_ringbuf_get(rb, ngtcp2_ringbuf_len(rb) - 1); + ngtcp2_dcid_copy(dcid, last); + ngtcp2_ringbuf_pop_back(rb); + } + + return 0; +} + +/* + * conn_recv_new_connection_id processes the incoming + * NEW_CONNECTION_ID frame |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * |fr| has the duplicated sequence number with different CID or + * token; or DCID is zero-length. + */ +static int conn_recv_new_connection_id(ngtcp2_conn *conn, + const ngtcp2_new_connection_id *fr) { + size_t i, len; + ngtcp2_dcid *dcid; + ngtcp2_pv *pv = conn->pv; + int rv; + int found = 0; + size_t extra_dcid = 0; + + if (conn->dcid.current.cid.datalen == 0) { + return NGTCP2_ERR_PROTO; + } + + if (fr->retire_prior_to > fr->seq) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + rv = ngtcp2_dcid_verify_uniqueness(&conn->dcid.current, fr->seq, &fr->cid, + fr->stateless_reset_token); + if (rv != 0) { + return rv; + } + if (ngtcp2_cid_eq(&conn->dcid.current.cid, &fr->cid)) { + found = 1; + } + + if (pv) { + rv = ngtcp2_dcid_verify_uniqueness(&pv->dcid, fr->seq, &fr->cid, + fr->stateless_reset_token); + if (rv != 0) { + return rv; + } + if (ngtcp2_cid_eq(&pv->dcid.cid, &fr->cid)) { + found = 1; + } + } + + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + rv = ngtcp2_dcid_verify_uniqueness(dcid, fr->seq, &fr->cid, + fr->stateless_reset_token); + if (rv != 0) { + return NGTCP2_ERR_PROTO; + } + if (ngtcp2_cid_eq(&dcid->cid, &fr->cid)) { + found = 1; + } + } + + len = ngtcp2_ringbuf_len(&conn->dcid.unused.rb); + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, i); + rv = ngtcp2_dcid_verify_uniqueness(dcid, fr->seq, &fr->cid, + fr->stateless_reset_token); + if (rv != 0) { + return NGTCP2_ERR_PROTO; + } + if (ngtcp2_cid_eq(&dcid->cid, &fr->cid)) { + found = 1; + } + } + + if (conn->dcid.retire_prior_to < fr->retire_prior_to) { + conn->dcid.retire_prior_to = fr->retire_prior_to; + + rv = conn_retire_dcid_prior_to(conn, &conn->dcid.bound.rb, + fr->retire_prior_to); + if (rv != 0) { + return rv; + } + + rv = conn_retire_dcid_prior_to(conn, &conn->dcid.unused.rb, + conn->dcid.retire_prior_to); + if (rv != 0) { + return rv; + } + } else if (fr->seq < conn->dcid.retire_prior_to) { + /* If packets are reordered, we might have retire_prior_to which + is larger than fr->seq. + + A malicious peer might send crafted NEW_CONNECTION_ID to force + local endpoint to create lots of RETIRE_CONNECTION_ID frames. + For example, a peer might send seq = 50000 and retire_prior_to + = 50000. Then send NEW_CONNECTION_ID frames with seq < + 50000. */ + return conn_retire_dcid_seq(conn, fr->seq); + } + + if (found) { + return 0; + } + + if (ngtcp2_gaptr_is_pushed(&conn->dcid.seqgap, fr->seq, 1)) { + return 0; + } + + rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, fr->seq, 1); + if (rv != 0) { + return rv; + } + + if (ngtcp2_ksl_len(&conn->dcid.seqgap.gap) > 32) { + ngtcp2_gaptr_drop_first_gap(&conn->dcid.seqgap); + } + + len = ngtcp2_ringbuf_len(&conn->dcid.unused.rb); + + if (conn->dcid.current.seq >= conn->dcid.retire_prior_to) { + ++extra_dcid; + } + if (pv) { + if (pv->dcid.seq != conn->dcid.current.seq && + pv->dcid.seq >= conn->dcid.retire_prior_to) { + ++extra_dcid; + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.seq != conn->dcid.current.seq && + pv->fallback_dcid.seq >= conn->dcid.retire_prior_to) { + ++extra_dcid; + } + } + + if (conn->local.transport_params.active_connection_id_limit <= + len + extra_dcid) { + return NGTCP2_ERR_CONNECTION_ID_LIMIT; + } + + if (len >= NGTCP2_MAX_DCID_POOL_SIZE) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "too many connection ID"); + return 0; + } + + dcid = ngtcp2_ringbuf_push_back(&conn->dcid.unused.rb); + ngtcp2_dcid_init(dcid, fr->seq, &fr->cid, fr->stateless_reset_token); + + return 0; +} + +/* + * conn_post_process_recv_new_connection_id handles retirement request + * of active DCIDs. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_post_process_recv_new_connection_id(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + ngtcp2_pv *pv = conn->pv; + ngtcp2_dcid *dcid; + int rv; + + if (conn->dcid.current.seq < conn->dcid.retire_prior_to) { + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return 0; + } + + rv = conn_retire_dcid(conn, &conn->dcid.current, ts); + if (rv != 0) { + return rv; + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + if (pv) { + if (conn->dcid.current.seq == pv->dcid.seq) { + ngtcp2_dcid_copy_cid_token(&pv->dcid, dcid); + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + conn->dcid.current.seq == pv->fallback_dcid.seq) { + ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, dcid); + } + } + + ngtcp2_dcid_copy_cid_token(&conn->dcid.current, dcid); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + rv = conn_call_activate_dcid(conn, &conn->dcid.current); + if (rv != 0) { + return rv; + } + } + + if (pv) { + if (pv->dcid.seq < conn->dcid.retire_prior_to) { + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb)) { + rv = conn_retire_dcid(conn, &pv->dcid, ts); + if (rv != 0) { + return rv; + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->dcid.seq == pv->fallback_dcid.seq) { + ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, dcid); + } + + ngtcp2_dcid_copy_cid_token(&pv->dcid, dcid); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + rv = conn_call_activate_dcid(conn, &pv->dcid); + if (rv != 0) { + return rv; + } + } else { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV, + "path migration is aborted because connection ID is" + "retired and no unused connection ID is available"); + + return conn_abort_pv(conn, ts); + } + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.seq < conn->dcid.retire_prior_to) { + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb)) { + rv = conn_retire_dcid(conn, &pv->fallback_dcid, ts); + if (rv != 0) { + return rv; + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ngtcp2_dcid_copy_cid_token(&pv->fallback_dcid, dcid); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + rv = conn_call_activate_dcid(conn, &pv->fallback_dcid); + if (rv != 0) { + return rv; + } + } else { + /* Now we have no fallback dcid. */ + return conn_abort_pv(conn, ts); + } + } + } + + return 0; +} + +/* + * conn_recv_retire_connection_id processes the incoming + * RETIRE_CONNECTION_ID frame |fr|. |hd| is a packet header which + * |fr| is included. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_PROTO + * SCID is zero-length. + * NGTCP2_ERR_FRAME_ENCODING + * Attempt to retire CID which is used as DCID to send this frame. + */ +static int conn_recv_retire_connection_id(ngtcp2_conn *conn, + const ngtcp2_pkt_hd *hd, + const ngtcp2_retire_connection_id *fr, + ngtcp2_tstamp ts) { + ngtcp2_ksl_it it; + ngtcp2_scid *scid; + + if (conn->oscid.datalen == 0 || conn->scid.last_seq < fr->seq) { + return NGTCP2_ERR_PROTO; + } + + for (it = ngtcp2_ksl_begin(&conn->scid.set); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + scid = ngtcp2_ksl_it_get(&it); + if (scid->seq == fr->seq) { + if (ngtcp2_cid_eq(&scid->cid, &hd->dcid)) { + return NGTCP2_ERR_PROTO; + } + + if (!(scid->flags & NGTCP2_SCID_FLAG_RETIRED)) { + scid->flags |= NGTCP2_SCID_FLAG_RETIRED; + ++conn->scid.num_retired; + } + + if (scid->pe.index != NGTCP2_PQ_BAD_INDEX) { + ngtcp2_pq_remove(&conn->scid.used, &scid->pe); + scid->pe.index = NGTCP2_PQ_BAD_INDEX; + } + + scid->retired_ts = ts; + + return ngtcp2_pq_push(&conn->scid.used, &scid->pe); + } + } + + return 0; +} + +/* + * conn_recv_new_token processes the incoming NEW_TOKEN frame |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Token is empty + * NGTCP2_ERR_PROTO: + * Server received NEW_TOKEN. + */ +static int conn_recv_new_token(ngtcp2_conn *conn, const ngtcp2_new_token *fr) { + if (conn->server) { + return NGTCP2_ERR_PROTO; + } + + if (fr->token.len == 0) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + return conn_call_recv_new_token(conn, &fr->token); +} + +/* + * conn_recv_streams_blocked_bidi processes the incoming + * STREAMS_BLOCKED (0x16). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Maximum Streams is larger than advertised value. + */ +static int conn_recv_streams_blocked_bidi(ngtcp2_conn *conn, + ngtcp2_streams_blocked *fr) { + if (fr->max_streams > conn->remote.bidi.max_streams) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + return 0; +} + +/* + * conn_recv_streams_blocked_uni processes the incoming + * STREAMS_BLOCKED (0x17). + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Maximum Streams is larger than advertised value. + */ +static int conn_recv_streams_blocked_uni(ngtcp2_conn *conn, + ngtcp2_streams_blocked *fr) { + if (fr->max_streams > conn->remote.uni.max_streams) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + return 0; +} + +/* + * conn_select_preferred_addr asks a client application to select a + * server address from preferred addresses received from server. If a + * client chooses the address, path validation will start. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_select_preferred_addr(ngtcp2_conn *conn) { + ngtcp2_path_storage ps; + int rv; + ngtcp2_duration pto, initial_pto, timeout; + ngtcp2_pv *pv; + ngtcp2_dcid *dcid; + + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return 0; + } + + ngtcp2_path_storage_zero(&ps); + ngtcp2_addr_copy(&ps.path.local, &conn->dcid.current.ps.path.local); + + rv = conn_call_select_preferred_addr(conn, &ps.path); + if (rv != 0) { + return rv; + } + + if (ps.path.remote.addrlen == 0 || + ngtcp2_addr_eq(&conn->dcid.current.ps.path.remote, &ps.path.remote)) { + return 0; + } + + assert(conn->pv == NULL); + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ngtcp2_dcid_set_path(dcid, &ps.path); + + pto = conn_compute_pto(conn, &conn->pktns); + initial_pto = conn_compute_initial_pto(conn, &conn->pktns); + timeout = 3 * ngtcp2_max(pto, initial_pto); + + rv = ngtcp2_pv_new(&pv, dcid, timeout, NGTCP2_PV_FLAG_PREFERRED_ADDR, + &conn->log, conn->mem); + if (rv != 0) { + /* TODO Call ngtcp2_dcid_free here if it is introduced */ + return rv; + } + + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + conn->pv = pv; + + return conn_call_activate_dcid(conn, &pv->dcid); +} + +/* + * conn_recv_handshake_done processes the incoming HANDSHAKE_DONE + * frame |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * Server received HANDSHAKE_DONE frame. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_recv_handshake_done(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + int rv; + + if (conn->server) { + return NGTCP2_ERR_PROTO; + } + + if (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) { + return 0; + } + + conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED | + NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + + conn->pktns.rtb.persistent_congestion_start_ts = ts; + + conn_discard_handshake_state(conn, ts); + + assert(conn->remote.transport_params); + + if (conn->remote.transport_params->preferred_address_present) { + rv = conn_select_preferred_addr(conn); + if (rv != 0) { + return rv; + } + } + + rv = conn_call_handshake_confirmed(conn); + if (rv != 0) { + return rv; + } + + /* Re-arm loss detection timer after handshake has been + confirmed. */ + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + return 0; +} + +/* + * conn_recv_datagram processes the incoming DATAGRAM frame |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +static int conn_recv_datagram(ngtcp2_conn *conn, ngtcp2_datagram *fr) { + assert(conn->local.transport_params.max_datagram_frame_size); + + return conn_call_recv_datagram(conn, fr); +} + +/* + * conn_key_phase_changed returns nonzero if |hd| indicates that the + * key phase has unexpected value. + */ +static int conn_key_phase_changed(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd) { + ngtcp2_pktns *pktns = &conn->pktns; + + return !(pktns->crypto.rx.ckm->flags & NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE) ^ + !(hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE); +} + +/* + * conn_prepare_key_update installs new updated keys. + */ +static int conn_prepare_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + int rv; + ngtcp2_tstamp confirmed_ts = conn->crypto.key_update.confirmed_ts; + ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns); + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_crypto_km *rx_ckm = pktns->crypto.rx.ckm; + ngtcp2_crypto_km *tx_ckm = pktns->crypto.tx.ckm; + ngtcp2_crypto_km *new_rx_ckm, *new_tx_ckm; + ngtcp2_crypto_aead_ctx rx_aead_ctx = {0}, tx_aead_ctx = {0}; + size_t secretlen, ivlen; + + if ((conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) && + tx_ckm->use_count >= pktns->crypto.ctx.max_encryption && + ngtcp2_conn_initiate_key_update(conn, ts) != 0) { + return NGTCP2_ERR_AEAD_LIMIT_REACHED; + } + + if ((conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) || + (confirmed_ts != UINT64_MAX && confirmed_ts + pto > ts)) { + return 0; + } + + if (conn->crypto.key_update.new_rx_ckm || + conn->crypto.key_update.new_tx_ckm) { + assert(conn->crypto.key_update.new_rx_ckm); + assert(conn->crypto.key_update.new_tx_ckm); + return 0; + } + + secretlen = rx_ckm->secret.len; + ivlen = rx_ckm->iv.len; + + rv = ngtcp2_crypto_km_nocopy_new(&conn->crypto.key_update.new_rx_ckm, + secretlen, ivlen, conn->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_crypto_km_nocopy_new(&conn->crypto.key_update.new_tx_ckm, + secretlen, ivlen, conn->mem); + if (rv != 0) { + return rv; + } + + new_rx_ckm = conn->crypto.key_update.new_rx_ckm; + new_tx_ckm = conn->crypto.key_update.new_tx_ckm; + + rv = conn_call_update_key( + conn, new_rx_ckm->secret.base, new_tx_ckm->secret.base, &rx_aead_ctx, + new_rx_ckm->iv.base, &tx_aead_ctx, new_tx_ckm->iv.base, + rx_ckm->secret.base, tx_ckm->secret.base, secretlen); + if (rv != 0) { + return rv; + } + + new_rx_ckm->aead_ctx = rx_aead_ctx; + new_tx_ckm->aead_ctx = tx_aead_ctx; + + if (!(rx_ckm->flags & NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE)) { + new_rx_ckm->flags |= NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE; + new_tx_ckm->flags |= NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE; + } + + if (conn->crypto.key_update.old_rx_ckm) { + conn_call_delete_crypto_aead_ctx( + conn, &conn->crypto.key_update.old_rx_ckm->aead_ctx); + ngtcp2_crypto_km_del(conn->crypto.key_update.old_rx_ckm, conn->mem); + conn->crypto.key_update.old_rx_ckm = NULL; + } + + return 0; +} + +/* + * conn_rotate_keys rotates keys. The current key moves to old key, + * and new key moves to the current key. If the local endpoint + * initiated this key update, pass nonzero as |initiator|. + */ +static void conn_rotate_keys(ngtcp2_conn *conn, int64_t pkt_num, + int initiator) { + ngtcp2_pktns *pktns = &conn->pktns; + + assert(conn->crypto.key_update.new_rx_ckm); + assert(conn->crypto.key_update.new_tx_ckm); + assert(!conn->crypto.key_update.old_rx_ckm); + assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); + + conn->crypto.key_update.old_rx_ckm = pktns->crypto.rx.ckm; + + pktns->crypto.rx.ckm = conn->crypto.key_update.new_rx_ckm; + conn->crypto.key_update.new_rx_ckm = NULL; + pktns->crypto.rx.ckm->pkt_num = pkt_num; + + assert(pktns->crypto.tx.ckm); + + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx); + ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem); + + pktns->crypto.tx.ckm = conn->crypto.key_update.new_tx_ckm; + conn->crypto.key_update.new_tx_ckm = NULL; + pktns->crypto.tx.ckm->pkt_num = pktns->tx.last_pkt_num + 1; + + conn->flags |= NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED; + if (initiator) { + conn->flags |= NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR; + } +} + +/* + * conn_path_validation_in_progress returns nonzero if path validation + * against |path| is underway. + */ +static int conn_path_validation_in_progress(ngtcp2_conn *conn, + const ngtcp2_path *path) { + ngtcp2_pv *pv = conn->pv; + + return pv && ngtcp2_path_eq(&pv->dcid.ps.path, path); +} + +/* + * conn_recv_non_probing_pkt_on_new_path is called when non-probing + * packet is received via new path. It starts path validation against + * the new path. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CONN_ID_BLOCKED + * No DCID is available + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_recv_non_probing_pkt_on_new_path(ngtcp2_conn *conn, + const ngtcp2_path *path, + size_t dgramlen, + int new_cid_used, + ngtcp2_tstamp ts) { + + ngtcp2_dcid dcid, *bound_dcid, *last; + ngtcp2_pv *pv; + int rv; + ngtcp2_duration pto, initial_pto, timeout; + int require_new_cid; + int local_addr_eq; + uint32_t remote_addr_cmp; + size_t len, i; + + assert(conn->server); + + if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + ngtcp2_path_eq(&conn->pv->fallback_dcid.ps.path, path)) { + /* If new path equals fallback path, that means connection + migrated back to the original path. Fallback path is + considered to be validated. */ + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PTV, + "path is migrated back to the original path"); + ngtcp2_dcid_copy(&conn->dcid.current, &conn->pv->fallback_dcid); + conn_reset_congestion_state(conn, ts); + conn->dcid.current.bytes_recv += dgramlen; + conn_reset_ecn_validation_state(conn); + + rv = conn_abort_pv(conn, ts); + if (rv != 0) { + return rv; + } + + /* Run PMTUD just in case if it is prematurely aborted */ + assert(!conn->pmtud); + + return conn_start_pmtud(conn); + } + + remote_addr_cmp = + ngtcp2_addr_compare(&conn->dcid.current.ps.path.remote, &path->remote); + local_addr_eq = + ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, &path->local); + + /* + * When to change DCID? RFC 9002 section 9.5 says: + * + * An endpoint MUST NOT reuse a connection ID when sending from more + * than one local address -- for example, when initiating connection + * migration as described in Section 9.2 or when probing a new + * network path as described in Section 9.1. + * + * Similarly, an endpoint MUST NOT reuse a connection ID when + * sending to more than one destination address. Due to network + * changes outside the control of its peer, an endpoint might + * receive packets from a new source address with the same + * Destination Connection ID field value, in which case it MAY + * continue to use the current connection ID with the new remote + * address while still sending from the same local address. + */ + require_new_cid = conn->dcid.current.cid.datalen && + ((new_cid_used && remote_addr_cmp) || !local_addr_eq); + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "non-probing packet was received from new remote address"); + + pto = conn_compute_pto(conn, &conn->pktns); + initial_pto = conn_compute_initial_pto(conn, &conn->pktns); + timeout = 3 * ngtcp2_max(pto, initial_pto); + + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + + for (i = 0; i < len; ++i) { + bound_dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + if (ngtcp2_path_eq(&bound_dcid->ps.path, path)) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_CON, + "Found DCID which has already been bound to the new path"); + + ngtcp2_dcid_copy(&dcid, bound_dcid); + if (i == 0) { + ngtcp2_ringbuf_pop_front(&conn->dcid.bound.rb); + } else if (i == ngtcp2_ringbuf_len(&conn->dcid.bound.rb) - 1) { + ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); + } else { + last = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, len - 1); + ngtcp2_dcid_copy(bound_dcid, last); + ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); + } + require_new_cid = 0; + + if (dcid.cid.datalen) { + rv = conn_call_activate_dcid(conn, &dcid); + if (rv != 0) { + return rv; + } + } + break; + } + } + + if (i == len) { + if (require_new_cid) { + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return NGTCP2_ERR_CONN_ID_BLOCKED; + } + ngtcp2_dcid_copy(&dcid, ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0)); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + rv = conn_call_activate_dcid(conn, &dcid); + if (rv != 0) { + return rv; + } + } else { + /* Use the current DCID if a remote endpoint does not change + DCID. */ + ngtcp2_dcid_copy(&dcid, &conn->dcid.current); + dcid.bytes_sent = 0; + dcid.bytes_recv = 0; + dcid.flags &= (uint8_t)~NGTCP2_DCID_FLAG_PATH_VALIDATED; + } + } + + ngtcp2_dcid_set_path(&dcid, path); + dcid.bytes_recv += dgramlen; + + rv = ngtcp2_pv_new(&pv, &dcid, timeout, NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE, + &conn->log, conn->mem); + if (rv != 0) { + return rv; + } + + if (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE)) { + ngtcp2_dcid_copy(&pv->fallback_dcid, &conn->pv->fallback_dcid); + pv->fallback_pto = conn->pv->fallback_pto; + /* Unset the flag bit so that conn_stop_pv does not retire + DCID. */ + conn->pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE; + } else { + ngtcp2_dcid_copy(&pv->fallback_dcid, &conn->dcid.current); + pv->fallback_pto = pto; + } + + if (!local_addr_eq || (remote_addr_cmp & (NGTCP2_ADDR_COMPARE_FLAG_ADDR | + NGTCP2_ADDR_COMPARE_FLAG_FAMILY))) { + conn_reset_congestion_state(conn, ts); + } else { + /* For NAT rebinding, keep max_udp_payload_size since client most + likely does not send a padded PATH_CHALLENGE. */ + dcid.max_udp_payload_size = ngtcp2_max( + dcid.max_udp_payload_size, conn->dcid.current.max_udp_payload_size); + } + + ngtcp2_dcid_copy(&conn->dcid.current, &dcid); + + conn_reset_ecn_validation_state(conn); + + ngtcp2_conn_stop_pmtud(conn); + + if (conn->pv) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_PTV, + "path migration is aborted because new migration has started"); + rv = conn_abort_pv(conn, ts); + if (rv != 0) { + return rv; + } + } + + conn->pv = pv; + + return 0; +} + +/* + * conn_recv_pkt_from_new_path is called when a 1RTT packet is + * received from new path (not current path). This packet would be a + * packet which only contains probing frame, or reordered packet, or a + * path is being validated. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CONN_ID_BLOCKED + * No unused DCID is available + * NGTCP2_ERR_NOMEM + * Out of memory + */ +static int conn_recv_pkt_from_new_path(ngtcp2_conn *conn, + const ngtcp2_path *path, size_t dgramlen, + int path_challenge_recved, + ngtcp2_tstamp ts) { + ngtcp2_pv *pv = conn->pv; + ngtcp2_dcid *bound_dcid; + int rv; + + if (pv) { + if (ngtcp2_path_eq(&pv->dcid.ps.path, path)) { + pv->dcid.bytes_recv += dgramlen; + return 0; + } + + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + ngtcp2_path_eq(&pv->fallback_dcid.ps.path, path)) { + pv->fallback_dcid.bytes_recv += dgramlen; + return 0; + } + } + + if (!path_challenge_recved) { + return 0; + } + + rv = conn_bind_dcid(conn, &bound_dcid, path, ts); + if (rv != 0) { + return rv; + } + + ngtcp2_dcid_set_path(bound_dcid, path); + bound_dcid->bytes_recv += dgramlen; + + return 0; +} + +/* + * conn_recv_delayed_handshake_pkt processes the received Handshake + * packet which is received after handshake completed. This function + * does the minimal job, and its purpose is send acknowledgement of + * this packet to the peer. We assume that hd->type == + * NGTCP2_PKT_HANDSHAKE. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Frame is badly formatted; or frame type is unknown. + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_DISCARD_PKT + * Packet was discarded. + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed. + * NGTCP2_ERR_PROTO + * Frame that is not allowed in Handshake packet is received. + */ +static int +conn_recv_delayed_handshake_pkt(ngtcp2_conn *conn, const ngtcp2_pkt_info *pi, + const ngtcp2_pkt_hd *hd, size_t pktlen, + const uint8_t *payload, size_t payloadlen, + ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + ngtcp2_max_frame mfr; + ngtcp2_frame *fr = &mfr.fr; + int rv; + int require_ack = 0; + ngtcp2_pktns *pktns; + + assert(hd->type == NGTCP2_PKT_HANDSHAKE); + + pktns = conn->hs_pktns; + + if (payloadlen == 0) { + /* QUIC packet must contain at least one frame */ + return NGTCP2_ERR_PROTO; + } + + ngtcp2_qlog_pkt_received_start(&conn->qlog); + + for (; payloadlen;) { + nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen); + if (nread < 0) { + return (int)nread; + } + + payload += nread; + payloadlen -= (size_t)nread; + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + fr->ack.ack_delay = 0; + fr->ack.ack_delay_unscaled = 0; + break; + } + + ngtcp2_log_rx_fr(&conn->log, hd, fr); + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if (!conn->server) { + conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + } + rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PADDING: + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + rv = conn_recv_connection_close(conn, &fr->connection_close); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_CRYPTO: + case NGTCP2_FRAME_PING: + require_ack = 1; + break; + default: + return NGTCP2_ERR_PROTO; + } + + ngtcp2_qlog_write_frame(&conn->qlog, fr); + } + + ngtcp2_qlog_pkt_received_end(&conn->qlog, hd, pktlen); + + rv = pktns_commit_recv_pkt_num(pktns, hd->pkt_num, require_ack, pkt_ts); + if (rv != 0) { + return rv; + } + + pktns_increase_ecn_counts(pktns, pi); + + if (require_ack && + (++pktns->acktr.rx_npkt >= conn->local.settings.ack_thresh || + (pi->ecn & NGTCP2_ECN_MASK) == NGTCP2_ECN_CE)) { + ngtcp2_acktr_immediate_ack(&pktns->acktr); + } + + rv = ngtcp2_conn_sched_ack(conn, &pktns->acktr, hd->pkt_num, require_ack, + pkt_ts); + if (rv != 0) { + return rv; + } + + conn_restart_timer_on_read(conn, ts); + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + return 0; +} + +/* + * conn_allow_path_change_under_disable_active_migration returns + * nonzero if a packet from |path| is acceptable under + * disable_active_migration is on. + */ +static int +conn_allow_path_change_under_disable_active_migration(ngtcp2_conn *conn, + const ngtcp2_path *path) { + uint32_t remote_addr_cmp; + const ngtcp2_preferred_addr *paddr; + ngtcp2_addr addr; + + assert(conn->server); + assert(conn->local.transport_params.disable_active_migration); + + /* If local address does not change, it must be passive migration + (NAT rebinding). */ + if (ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, &path->local)) { + remote_addr_cmp = + ngtcp2_addr_compare(&conn->dcid.current.ps.path.remote, &path->remote); + + return (remote_addr_cmp | NGTCP2_ADDR_COMPARE_FLAG_PORT) == + NGTCP2_ADDR_COMPARE_FLAG_PORT; + } + + /* If local address changes, it must be one of the preferred + addresses. */ + + if (!conn->local.transport_params.preferred_address_present) { + return 0; + } + + paddr = &conn->local.transport_params.preferred_address; + + if (paddr->ipv4_present) { + ngtcp2_addr_init(&addr, (const ngtcp2_sockaddr *)&paddr->ipv4, + sizeof(paddr->ipv4)); + + if (ngtcp2_addr_eq(&addr, &path->local)) { + return 1; + } + } + + if (paddr->ipv6_present) { + ngtcp2_addr_init(&addr, (const ngtcp2_sockaddr *)&paddr->ipv6, + sizeof(paddr->ipv6)); + + if (ngtcp2_addr_eq(&addr, &path->local)) { + return 1; + } + } + + return 0; +} + +/* + * conn_recv_pkt processes a packet contained in the buffer pointed by + * |pkt| of length |pktlen|. |pkt| may contain multiple QUIC packets. + * This function only processes the first packet. |pkt_ts| is the + * timestamp when packet is received. |ts| should be the current + * time. Usually they are the same, but for buffered packets, + * |pkt_ts| would be earlier than |ts|. + * + * This function returns the number of bytes processed if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_DISCARD_PKT + * Packet was discarded because plain text header was malformed; + * or its payload could not be decrypted. + * NGTCP2_ERR_PROTO + * Packet is badly formatted; or 0RTT packet contains other than + * PADDING or STREAM frames; or other QUIC protocol violation is + * found. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_FRAME_ENCODING + * Frame is badly formatted; or frame type is unknown. + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed. + * NGTCP2_ERR_STREAM_STATE + * Frame is received to the local stream which is not initiated. + * NGTCP2_ERR_STREAM_LIMIT + * Frame has remote stream ID which is strictly greater than the + * allowed limit. + * NGTCP2_ERR_FLOW_CONTROL + * Flow control limit is violated. + * NGTCP2_ERR_FINAL_SIZE + * Frame has strictly larger end offset than it is permitted. + */ +static ngtcp2_ssize conn_recv_pkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, + ngtcp2_tstamp pkt_ts, ngtcp2_tstamp ts) { + ngtcp2_pkt_hd hd; + int rv = 0; + size_t hdpktlen; + const uint8_t *payload; + size_t payloadlen; + ngtcp2_ssize nread, nwrite; + ngtcp2_max_frame mfr; + ngtcp2_frame *fr = &mfr.fr; + int require_ack = 0; + ngtcp2_crypto_aead *aead; + ngtcp2_crypto_cipher *hp; + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx *hp_ctx; + ngtcp2_hp_mask hp_mask; + ngtcp2_decrypt decrypt; + ngtcp2_pktns *pktns; + int non_probing_pkt = 0; + int key_phase_bit_changed = 0; + int force_decrypt_failure = 0; + int recv_ncid = 0; + int new_cid_used = 0; + int path_challenge_recved = 0; + + if (conn->server && conn->local.transport_params.disable_active_migration && + !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) && + !conn_allow_path_change_under_disable_active_migration(conn, path)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet is discarded because active migration is disabled"); + + return NGTCP2_ERR_DISCARD_PKT; + } + + if (pkt[0] & NGTCP2_HEADER_FORM_BIT) { + nread = ngtcp2_pkt_decode_hd_long(&hd, pkt, pktlen); + if (nread < 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decode long header"); + return NGTCP2_ERR_DISCARD_PKT; + } + + if (pktlen < (size_t)nread + hd.len) { + return NGTCP2_ERR_DISCARD_PKT; + } + + assert(conn->negotiated_version); + + if (hd.version != conn->client_chosen_version && + hd.version != conn->negotiated_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + pktlen = (size_t)nread + hd.len; + + /* Quoted from spec: if subsequent packets of those types include + a different Source Connection ID, they MUST be discarded. */ + if (!ngtcp2_cid_eq(&conn->dcid.current.cid, &hd.scid)) { + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched SCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + + switch (hd.type) { + case NGTCP2_PKT_INITIAL: + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "delayed Initial packet was discarded"); + return (ngtcp2_ssize)pktlen; + case NGTCP2_PKT_HANDSHAKE: + if (hd.version != conn->negotiated_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!conn->hs_pktns) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "delayed Handshake packet was discarded"); + return (ngtcp2_ssize)pktlen; + } + + pktns = conn->hs_pktns; + aead = &pktns->crypto.ctx.aead; + hp = &pktns->crypto.ctx.hp; + ckm = pktns->crypto.rx.ckm; + hp_ctx = &pktns->crypto.rx.hp_ctx; + hp_mask = conn->callbacks.hp_mask; + decrypt = conn->callbacks.decrypt; + break; + case NGTCP2_PKT_0RTT: + if (!conn->server || hd.version != conn->client_chosen_version) { + return NGTCP2_ERR_DISCARD_PKT; + } + + if (!conn->early.ckm) { + return (ngtcp2_ssize)pktlen; + } + + pktns = &conn->pktns; + aead = &conn->early.ctx.aead; + hp = &conn->early.ctx.hp; + ckm = conn->early.ckm; + hp_ctx = &conn->early.hp_ctx; + hp_mask = conn->callbacks.hp_mask; + decrypt = conn->callbacks.decrypt; + break; + default: + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet type 0x%02x was ignored", hd.type); + return (ngtcp2_ssize)pktlen; + } + } else { + nread = ngtcp2_pkt_decode_hd_short(&hd, pkt, pktlen, conn->oscid.datalen); + if (nread < 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decode short header"); + return NGTCP2_ERR_DISCARD_PKT; + } + + pktns = &conn->pktns; + aead = &pktns->crypto.ctx.aead; + hp = &pktns->crypto.ctx.hp; + ckm = pktns->crypto.rx.ckm; + hp_ctx = &pktns->crypto.rx.hp_ctx; + hp_mask = conn->callbacks.hp_mask; + decrypt = conn->callbacks.decrypt; + } + + rv = conn_ensure_decrypt_hp_buffer(conn, (size_t)nread + 4); + if (rv != 0) { + return rv; + } + + nwrite = decrypt_hp(&hd, conn->crypto.decrypt_hp_buf.base, hp, pkt, pktlen, + (size_t)nread, hp_ctx, hp_mask); + if (nwrite < 0) { + if (ngtcp2_err_is_fatal((int)nwrite)) { + return nwrite; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet number"); + return NGTCP2_ERR_DISCARD_PKT; + } + + hdpktlen = (size_t)nwrite; + payload = pkt + hdpktlen; + payloadlen = pktlen - hdpktlen; + + hd.pkt_num = ngtcp2_pkt_adjust_pkt_num(pktns->rx.max_pkt_num, hd.pkt_num, + pkt_num_bits(hd.pkt_numlen)); + if (hd.pkt_num > NGTCP2_MAX_PKT_NUM) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "pkn=%" PRId64 " is greater than maximum pkn", hd.pkt_num); + return NGTCP2_ERR_DISCARD_PKT; + } + + ngtcp2_log_rx_pkt_hd(&conn->log, &hd); + + if (hd.type == NGTCP2_PKT_1RTT) { + key_phase_bit_changed = conn_key_phase_changed(conn, &hd); + } + + rv = conn_ensure_decrypt_buffer(conn, payloadlen); + if (rv != 0) { + return rv; + } + + if (key_phase_bit_changed) { + assert(hd.type == NGTCP2_PKT_1RTT); + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, "unexpected KEY_PHASE"); + + if (ckm->pkt_num > hd.pkt_num) { + if (conn->crypto.key_update.old_rx_ckm) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "decrypting with old key"); + ckm = conn->crypto.key_update.old_rx_ckm; + } else { + force_decrypt_failure = 1; + } + } else if (pktns->rx.max_pkt_num < hd.pkt_num) { + assert(ckm->pkt_num < hd.pkt_num); + if (!conn->crypto.key_update.new_rx_ckm) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "new key is not available"); + force_decrypt_failure = 1; + } else { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "decrypting with new key"); + ckm = conn->crypto.key_update.new_rx_ckm; + } + } else { + force_decrypt_failure = 1; + } + } + + nwrite = decrypt_pkt(conn->crypto.decrypt_buf.base, aead, payload, payloadlen, + conn->crypto.decrypt_hp_buf.base, hdpktlen, hd.pkt_num, + ckm, decrypt); + + if (force_decrypt_failure) { + nwrite = NGTCP2_ERR_DECRYPT; + } + + if (nwrite < 0) { + if (ngtcp2_err_is_fatal((int)nwrite)) { + return nwrite; + } + + assert(NGTCP2_ERR_DECRYPT == nwrite); + + if (hd.type == NGTCP2_PKT_1RTT && + ++conn->crypto.decryption_failure_count >= + pktns->crypto.ctx.max_decryption_failure) { + return NGTCP2_ERR_AEAD_LIMIT_REACHED; + } + + if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet payload"); + return NGTCP2_ERR_DISCARD_PKT; + } + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "could not decrypt packet payload"); + return NGTCP2_ERR_DISCARD_PKT; + } + + rv = ngtcp2_pkt_verify_reserved_bits(conn->crypto.decrypt_hp_buf.base[0]); + if (rv != 0) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet has incorrect reserved bits"); + + return NGTCP2_ERR_PROTO; + } + + if (pktns_pkt_num_is_duplicate(pktns, hd.pkt_num)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was discarded because of duplicated packet number"); + return NGTCP2_ERR_DISCARD_PKT; + } + + payload = conn->crypto.decrypt_buf.base; + payloadlen = (size_t)nwrite; + + if (payloadlen == 0) { + /* QUIC packet must contain at least one frame */ + return NGTCP2_ERR_PROTO; + } + + if (hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) { + switch (hd.type) { + case NGTCP2_PKT_HANDSHAKE: + rv = conn_verify_dcid(conn, NULL, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + + rv = conn_recv_delayed_handshake_pkt(conn, pi, &hd, pktlen, payload, + payloadlen, pkt_ts, ts); + if (rv < 0) { + return (ngtcp2_ssize)rv; + } + + return (ngtcp2_ssize)pktlen; + case NGTCP2_PKT_0RTT: + if (!ngtcp2_cid_eq(&conn->rcid, &hd.dcid)) { + rv = conn_verify_dcid(conn, NULL, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + } + break; + default: + /* Unreachable */ + ngtcp2_unreachable(); + } + } else { + rv = conn_verify_dcid(conn, &new_cid_used, &hd); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "packet was ignored because of mismatched DCID"); + return NGTCP2_ERR_DISCARD_PKT; + } + } + + ngtcp2_qlog_pkt_received_start(&conn->qlog); + + for (; payloadlen;) { + nread = ngtcp2_pkt_decode_frame(fr, payload, payloadlen); + if (nread < 0) { + return nread; + } + + payload += nread; + payloadlen -= (size_t)nread; + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if ((hd.flags & NGTCP2_PKT_FLAG_LONG_FORM) && + hd.type == NGTCP2_PKT_0RTT) { + return NGTCP2_ERR_PROTO; + } + assert(conn->remote.transport_params); + assign_recved_ack_delay_unscaled( + &fr->ack, conn->remote.transport_params->ack_delay_exponent); + break; + } + + ngtcp2_log_rx_fr(&conn->log, &hd, fr); + + if (hd.type == NGTCP2_PKT_0RTT) { + switch (fr->type) { + case NGTCP2_FRAME_PADDING: + case NGTCP2_FRAME_PING: + case NGTCP2_FRAME_RESET_STREAM: + case NGTCP2_FRAME_STOP_SENDING: + case NGTCP2_FRAME_STREAM: + case NGTCP2_FRAME_MAX_DATA: + case NGTCP2_FRAME_MAX_STREAM_DATA: + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + case NGTCP2_FRAME_DATA_BLOCKED: + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + case NGTCP2_FRAME_NEW_CONNECTION_ID: + case NGTCP2_FRAME_PATH_CHALLENGE: + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + break; + default: + return NGTCP2_ERR_PROTO; + } + } + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + case NGTCP2_FRAME_PADDING: + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + break; + default: + require_ack = 1; + } + + switch (fr->type) { + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if (!conn->server) { + conn->flags |= NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED; + } + rv = conn_recv_ack(conn, pktns, &fr->ack, pkt_ts, ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STREAM: + rv = conn_recv_stream(conn, &fr->stream); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_CRYPTO: + rv = conn_recv_crypto(conn, NGTCP2_CRYPTO_LEVEL_APPLICATION, + &pktns->crypto.strm, &fr->crypto); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_RESET_STREAM: + rv = conn_recv_reset_stream(conn, &fr->reset_stream); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STOP_SENDING: + rv = conn_recv_stop_sending(conn, &fr->stop_sending); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_MAX_STREAM_DATA: + rv = conn_recv_max_stream_data(conn, &fr->max_stream_data); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_MAX_DATA: + conn_recv_max_data(conn, &fr->max_data); + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + rv = conn_recv_max_streams(conn, &fr->max_streams); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + rv = conn_recv_connection_close(conn, &fr->connection_close); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_PING: + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_PATH_CHALLENGE: + conn_recv_path_challenge(conn, path, &fr->path_challenge); + path_challenge_recved = 1; + break; + case NGTCP2_FRAME_PATH_RESPONSE: + rv = conn_recv_path_response(conn, &fr->path_response, ts); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_NEW_CONNECTION_ID: + rv = conn_recv_new_connection_id(conn, &fr->new_connection_id); + if (rv != 0) { + return rv; + } + recv_ncid = 1; + break; + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + rv = conn_recv_retire_connection_id(conn, &hd, &fr->retire_connection_id, + ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_NEW_TOKEN: + rv = conn_recv_new_token(conn, &fr->new_token); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_HANDSHAKE_DONE: + rv = conn_recv_handshake_done(conn, ts); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + rv = conn_recv_streams_blocked_bidi(conn, &fr->streams_blocked); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + rv = conn_recv_streams_blocked_uni(conn, &fr->streams_blocked); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_DATA_BLOCKED: + /* TODO Not implemented yet */ + non_probing_pkt = 1; + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + if ((uint64_t)nread > + conn->local.transport_params.max_datagram_frame_size) { + return NGTCP2_ERR_PROTO; + } + rv = conn_recv_datagram(conn, &fr->datagram); + if (rv != 0) { + return rv; + } + non_probing_pkt = 1; + break; + } + + ngtcp2_qlog_write_frame(&conn->qlog, fr); + } + + ngtcp2_qlog_pkt_received_end(&conn->qlog, &hd, pktlen); + + if (recv_ncid) { + rv = conn_post_process_recv_new_connection_id(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (conn->server && hd.type == NGTCP2_PKT_1RTT && + !ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + if (non_probing_pkt && pktns->rx.max_pkt_num < hd.pkt_num && + !conn_path_validation_in_progress(conn, path)) { + rv = conn_recv_non_probing_pkt_on_new_path(conn, path, dgramlen, + new_cid_used, ts); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + + /* DCID is not available. Just continue. */ + assert(NGTCP2_ERR_CONN_ID_BLOCKED == rv); + } + } else { + rv = conn_recv_pkt_from_new_path(conn, path, dgramlen, + path_challenge_recved, ts); + if (rv != 0) { + if (ngtcp2_err_is_fatal(rv)) { + return rv; + } + + /* DCID is not available. Just continue. */ + assert(NGTCP2_ERR_CONN_ID_BLOCKED == rv); + } + } + } + + if (hd.type == NGTCP2_PKT_1RTT) { + if (ckm == conn->crypto.key_update.new_rx_ckm) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "rotate keys"); + conn_rotate_keys(conn, hd.pkt_num, /* initiator = */ 0); + } else if (ckm->pkt_num > hd.pkt_num) { + ckm->pkt_num = hd.pkt_num; + } + + if (conn->server && conn->early.ckm && + conn->early.discard_started_ts == UINT64_MAX) { + conn->early.discard_started_ts = ts; + } + + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + conn_update_keep_alive_last_ts(conn, ts); + } + } + + rv = pktns_commit_recv_pkt_num(pktns, hd.pkt_num, require_ack, pkt_ts); + if (rv != 0) { + return rv; + } + + pktns_increase_ecn_counts(pktns, pi); + + if (require_ack && + (++pktns->acktr.rx_npkt >= conn->local.settings.ack_thresh || + (pi->ecn & NGTCP2_ECN_MASK) == NGTCP2_ECN_CE)) { + ngtcp2_acktr_immediate_ack(&pktns->acktr); + } + + rv = ngtcp2_conn_sched_ack(conn, &pktns->acktr, hd.pkt_num, require_ack, + pkt_ts); + if (rv != 0) { + return rv; + } + + conn_restart_timer_on_read(conn, ts); + + ngtcp2_qlog_metrics_updated(&conn->qlog, &conn->cstat); + + return conn->state == NGTCP2_CS_DRAINING ? NGTCP2_ERR_DRAINING + : (ngtcp2_ssize)pktlen; +} + +/* + * conn_process_buffered_protected_pkt processes buffered 0RTT or 1RTT + * packets. + * + * This function returns 0 if it succeeds, or the same negative error + * codes from conn_recv_pkt. + */ +static int conn_process_buffered_protected_pkt(ngtcp2_conn *conn, + ngtcp2_pktns *pktns, + ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + ngtcp2_pkt_chain **ppc, *next; + int rv; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "processing buffered protected packet"); + + for (ppc = &pktns->rx.buffed_pkts; *ppc;) { + next = (*ppc)->next; + nread = conn_recv_pkt(conn, &(*ppc)->path.path, &(*ppc)->pi, (*ppc)->pkt, + (*ppc)->pktlen, (*ppc)->dgramlen, (*ppc)->ts, ts); + if (nread < 0 && !ngtcp2_err_is_fatal((int)nread) && + nread != NGTCP2_ERR_DRAINING) { + /* TODO We don't know this is the first QUIC packet in a + datagram. */ + rv = conn_on_stateless_reset(conn, &(*ppc)->path.path, (*ppc)->pkt, + (*ppc)->pktlen); + if (rv == 0) { + ngtcp2_pkt_chain_del(*ppc, conn->mem); + *ppc = next; + return NGTCP2_ERR_DRAINING; + } + } + + ngtcp2_pkt_chain_del(*ppc, conn->mem); + *ppc = next; + if (nread < 0) { + if (nread == NGTCP2_ERR_DISCARD_PKT) { + continue; + } + return (int)nread; + } + } + + return 0; +} + +/* + * conn_process_buffered_handshake_pkt processes buffered Handshake + * packets. + * + * This function returns 0 if it succeeds, or the same negative error + * codes from conn_recv_handshake_pkt. + */ +static int conn_process_buffered_handshake_pkt(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + ngtcp2_pktns *pktns = conn->hs_pktns; + ngtcp2_ssize nread; + ngtcp2_pkt_chain **ppc, *next; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "processing buffered handshake packet"); + + for (ppc = &pktns->rx.buffed_pkts; *ppc;) { + next = (*ppc)->next; + nread = conn_recv_handshake_pkt(conn, &(*ppc)->path.path, &(*ppc)->pi, + (*ppc)->pkt, (*ppc)->pktlen, + (*ppc)->dgramlen, (*ppc)->ts, ts); + ngtcp2_pkt_chain_del(*ppc, conn->mem); + *ppc = next; + if (nread < 0) { + if (nread == NGTCP2_ERR_DISCARD_PKT) { + continue; + } + return (int)nread; + } + } + + return 0; +} + +static void conn_sync_stream_id_limit(ngtcp2_conn *conn) { + ngtcp2_transport_params *params = conn->remote.transport_params; + + assert(params); + + conn->local.bidi.max_streams = params->initial_max_streams_bidi; + conn->local.uni.max_streams = params->initial_max_streams_uni; +} + +static int strm_set_max_offset(void *data, void *ptr) { + ngtcp2_conn *conn = ptr; + ngtcp2_transport_params *params = conn->remote.transport_params; + ngtcp2_strm *strm = data; + uint64_t max_offset; + int rv; + + assert(params); + + if (!conn_local_stream(conn, strm->stream_id)) { + return 0; + } + + if (bidi_stream(strm->stream_id)) { + max_offset = params->initial_max_stream_data_bidi_remote; + } else { + max_offset = params->initial_max_stream_data_uni; + } + + if (strm->tx.max_offset < max_offset) { + strm->tx.max_offset = max_offset; + + /* Don't call callback if stream is half-closed local */ + if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) { + return 0; + } + + rv = conn_call_extend_max_stream_data(conn, strm, strm->stream_id, + strm->tx.max_offset); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +static int conn_sync_stream_data_limit(ngtcp2_conn *conn) { + return ngtcp2_map_each(&conn->strms, strm_set_max_offset, conn); +} + +/* + * conn_handshake_completed is called once cryptographic handshake has + * completed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed. + */ +static int conn_handshake_completed(ngtcp2_conn *conn) { + int rv; + + conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED; + + rv = conn_call_handshake_completed(conn); + if (rv != 0) { + return rv; + } + + if (conn->local.bidi.max_streams > 0) { + rv = conn_call_extend_max_local_streams_bidi(conn, + conn->local.bidi.max_streams); + if (rv != 0) { + return rv; + } + } + if (conn->local.uni.max_streams > 0) { + rv = conn_call_extend_max_local_streams_uni(conn, + conn->local.uni.max_streams); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +/* + * conn_recv_cpkt processes compound packet after handshake. The + * buffer pointed by |pkt| might contain multiple packets. The 1RTT + * packet must be the last one because it does not have payload length + * field. + * + * This function returns 0 if it succeeds, or the same negative error + * codes from conn_recv_pkt except for NGTCP2_ERR_DISCARD_PKT. + */ +static int conn_recv_cpkt(ngtcp2_conn *conn, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, ngtcp2_tstamp ts) { + ngtcp2_ssize nread; + int rv; + const uint8_t *origpkt = pkt; + size_t dgramlen = pktlen; + + if (ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + conn->dcid.current.bytes_recv += dgramlen; + } + + while (pktlen) { + nread = conn_recv_pkt(conn, path, pi, pkt, pktlen, dgramlen, ts, ts); + if (nread < 0) { + if (ngtcp2_err_is_fatal((int)nread)) { + return (int)nread; + } + + if (nread == NGTCP2_ERR_DRAINING) { + return NGTCP2_ERR_DRAINING; + } + + if (origpkt == pkt) { + rv = conn_on_stateless_reset(conn, path, origpkt, dgramlen); + if (rv == 0) { + return NGTCP2_ERR_DRAINING; + } + } + if (nread == NGTCP2_ERR_DISCARD_PKT) { + return 0; + } + return (int)nread; + } + + assert(pktlen >= (size_t)nread); + pkt += nread; + pktlen -= (size_t)nread; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_PKT, + "read packet %td left %zu", nread, pktlen); + } + + return 0; +} + +/* + * conn_is_retired_path returns nonzero if |path| is included in + * retired path list. + */ +static int conn_is_retired_path(ngtcp2_conn *conn, const ngtcp2_path *path) { + size_t i, len = ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + ngtcp2_dcid *dcid; + + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, i); + if (ngtcp2_path_eq(&dcid->ps.path, path)) { + return 1; + } + } + + return 0; +} + +/* + * conn_enqueue_handshake_done enqueues HANDSHAKE_DONE frame for + * transmission. + */ +static int conn_enqueue_handshake_done(ngtcp2_conn *conn) { + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_frame_chain *nfrc; + int rv; + + assert(conn->server); + + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + nfrc->fr.type = NGTCP2_FRAME_HANDSHAKE_DONE; + nfrc->next = pktns->tx.frq; + pktns->tx.frq = nfrc; + + return 0; +} + +/** + * @function + * + * `conn_read_handshake` performs QUIC cryptographic handshake by + * reading given data. |pkt| points to the buffer to read and + * |pktlen| is the length of the buffer. |path| is the network path. + * + * This function returns the number of bytes processed. Unless the + * last packet is 1RTT packet and an application decryption key has + * been installed, it returns |pktlen| if it succeeds. If it finds + * 1RTT packet and an application decryption key has been installed, + * it returns the number of bytes just before 1RTT packet begins. + * + * This function returns the number of bytes processed if it succeeds, + * or one of the following negative error codes: (TBD). + */ +static ngtcp2_ssize conn_read_handshake(ngtcp2_conn *conn, + const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_ssize nread; + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + /* TODO Better to log something when we ignore input */ + return (ngtcp2_ssize)pktlen; + case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE: + nread = conn_recv_handshake_cpkt(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return nread; + } + + if (conn->state == NGTCP2_CS_CLIENT_INITIAL) { + /* Retry packet was received */ + return (ngtcp2_ssize)pktlen; + } + + assert(conn->hs_pktns); + + if (conn->hs_pktns->crypto.rx.ckm && conn->in_pktns) { + rv = conn_process_buffered_handshake_pkt(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (conn_is_handshake_completed(conn) && + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED)) { + rv = conn_handshake_completed(conn); + if (rv != 0) { + return rv; + } + + rv = conn_process_buffered_protected_pkt(conn, &conn->pktns, ts); + if (rv != 0) { + return rv; + } + } + + return nread; + case NGTCP2_CS_SERVER_INITIAL: + nread = conn_recv_handshake_cpkt(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return nread; + } + + /* + * Client ServerHello might not fit into single Initial packet + * (e.g., resuming session with client authentication). If we get + * Client Initial which does not increase offset or it is 0RTT + * packet buffered, perform address validation in order to buffer + * validated data only. + */ + if (ngtcp2_strm_rx_offset(&conn->in_pktns->crypto.strm) == 0) { + if (conn->in_pktns->crypto.strm.rx.rob && + ngtcp2_rob_data_buffered(conn->in_pktns->crypto.strm.rx.rob)) { + /* Address has been validated with token */ + if (conn->local.settings.token.len) { + return nread; + } + return NGTCP2_ERR_RETRY; + } + if (conn->in_pktns->rx.buffed_pkts) { + /* 0RTT is buffered, force retry */ + return NGTCP2_ERR_RETRY; + } + /* If neither CRYPTO frame nor 0RTT packet is processed, just + drop connection. */ + return NGTCP2_ERR_DROP_CONN; + } + + /* Process re-ordered 0-RTT packets which arrived before Initial + packet. */ + if (conn->early.ckm) { + assert(conn->in_pktns); + + rv = conn_process_buffered_protected_pkt(conn, conn->in_pktns, ts); + if (rv != 0) { + return rv; + } + } + + return nread; + case NGTCP2_CS_SERVER_WAIT_HANDSHAKE: + nread = conn_recv_handshake_cpkt(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return nread; + } + + if (conn->hs_pktns->crypto.rx.ckm) { + rv = conn_process_buffered_handshake_pkt(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (conn->hs_pktns->rx.max_pkt_num != -1) { + conn_discard_initial_state(conn, ts); + } + + if (!conn_is_handshake_completed(conn)) { + /* If server hits amplification limit, it cancels loss detection + timer. If server receives a packet from client, the limit is + increased and server can send more. If server has + ack-eliciting Initial or Handshake packets, it should resend + it if timer fired but timer is not armed in this case. So + instead of resending Initial/Handshake packets, if server has + 1RTT data to send, it might send them and then might hit + amplification limit again until it hits stream data limit. + Initial/Handshake data is not resent. In order to avoid this + situation, try to arm loss detection and check the expiry + here so that on next write call, we can resend + Initial/Handshake first. */ + if (conn->cstat.loss_detection_timer == UINT64_MAX) { + ngtcp2_conn_set_loss_detection_timer(conn, ts); + if (ngtcp2_conn_loss_detection_expiry(conn) <= ts) { + rv = ngtcp2_conn_on_loss_detection_timer(conn, ts); + if (rv != 0) { + return rv; + } + } + } + + if ((size_t)nread < pktlen) { + /* We have 1RTT packet and application rx key, but the + handshake has not completed yet. */ + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "buffering 1RTT packet len=%zu", + pktlen - (size_t)nread); + + rv = conn_buffer_pkt(conn, &conn->pktns, path, pi, pkt + nread, + pktlen - (size_t)nread, pktlen, ts); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + return rv; + } + + return (ngtcp2_ssize)pktlen; + } + + return nread; + } + + if (!(conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED)) { + return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM; + } + + rv = conn_handshake_completed(conn); + if (rv != 0) { + return rv; + } + conn->state = NGTCP2_CS_POST_HANDSHAKE; + + rv = conn_call_activate_dcid(conn, &conn->dcid.current); + if (rv != 0) { + return rv; + } + + rv = conn_process_buffered_protected_pkt(conn, &conn->pktns, ts); + if (rv != 0) { + return rv; + } + + conn_discard_handshake_state(conn, ts); + + rv = conn_enqueue_handshake_done(conn); + if (rv != 0) { + return rv; + } + + if (!conn->local.settings.no_pmtud) { + rv = conn_start_pmtud(conn); + if (rv != 0) { + return rv; + } + } + + conn->pktns.rtb.persistent_congestion_start_ts = ts; + + /* Re-arm loss detection timer here after handshake has been + confirmed. */ + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + return nread; + case NGTCP2_CS_CLOSING: + return NGTCP2_ERR_CLOSING; + case NGTCP2_CS_DRAINING: + return NGTCP2_ERR_DRAINING; + default: + return (ngtcp2_ssize)pktlen; + } +} + +int ngtcp2_conn_read_pkt_versioned(ngtcp2_conn *conn, const ngtcp2_path *path, + int pkt_info_version, + const ngtcp2_pkt_info *pi, + const uint8_t *pkt, size_t pktlen, + ngtcp2_tstamp ts) { + int rv = 0; + ngtcp2_ssize nread = 0; + const ngtcp2_pkt_info zero_pi = {0}; + (void)pkt_info_version; + + assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); + + conn->log.last_ts = ts; + conn->qlog.last_ts = ts; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "recv packet len=%zu", + pktlen); + + if (pktlen == 0) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + /* client does not expect a packet from unknown path. */ + if (!conn->server && !ngtcp2_path_eq(&conn->dcid.current.ps.path, path) && + (!conn->pv || !ngtcp2_path_eq(&conn->pv->dcid.ps.path, path)) && + !conn_is_retired_path(conn, path)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "ignore packet from unknown path"); + return 0; + } + + if (!pi) { + pi = &zero_pi; + } + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE: + case NGTCP2_CS_CLIENT_TLS_HANDSHAKE_FAILED: + nread = conn_read_handshake(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return (int)nread; + } + + if ((size_t)nread == pktlen) { + return 0; + } + + assert(conn->pktns.crypto.rx.ckm); + + pkt += nread; + pktlen -= (size_t)nread; + + break; + case NGTCP2_CS_SERVER_INITIAL: + case NGTCP2_CS_SERVER_WAIT_HANDSHAKE: + case NGTCP2_CS_SERVER_TLS_HANDSHAKE_FAILED: + if (!ngtcp2_path_eq(&conn->dcid.current.ps.path, path)) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "ignore packet from unknown path during handshake"); + + if (conn->state == NGTCP2_CS_SERVER_INITIAL && + ngtcp2_strm_rx_offset(&conn->in_pktns->crypto.strm) == 0 && + (!conn->in_pktns->crypto.strm.rx.rob || + !ngtcp2_rob_data_buffered(conn->in_pktns->crypto.strm.rx.rob))) { + return NGTCP2_ERR_DROP_CONN; + } + + return 0; + } + + nread = conn_read_handshake(conn, path, pi, pkt, pktlen, ts); + if (nread < 0) { + return (int)nread; + } + + if ((size_t)nread == pktlen) { + return 0; + } + + assert(conn->pktns.crypto.rx.ckm); + + pkt += nread; + pktlen -= (size_t)nread; + + break; + case NGTCP2_CS_CLOSING: + return NGTCP2_ERR_CLOSING; + case NGTCP2_CS_DRAINING: + return NGTCP2_ERR_DRAINING; + case NGTCP2_CS_POST_HANDSHAKE: + rv = conn_prepare_key_update(conn, ts); + if (rv != 0) { + return rv; + } + break; + default: + ngtcp2_unreachable(); + } + + return conn_recv_cpkt(conn, path, pi, pkt, pktlen, ts); +} + +/* + * conn_check_pkt_num_exhausted returns nonzero if packet number is + * exhausted in at least one of packet number space. + */ +static int conn_check_pkt_num_exhausted(ngtcp2_conn *conn) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + + return (in_pktns && in_pktns->tx.last_pkt_num == NGTCP2_MAX_PKT_NUM) || + (hs_pktns && hs_pktns->tx.last_pkt_num == NGTCP2_MAX_PKT_NUM) || + conn->pktns.tx.last_pkt_num == NGTCP2_MAX_PKT_NUM; +} + +/* + * conn_retransmit_retry_early retransmits 0RTT packet after Retry is + * received from server. + */ +static ngtcp2_ssize conn_retransmit_retry_early(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint8_t flags, + ngtcp2_tstamp ts) { + return conn_write_pkt(conn, pi, dest, destlen, NULL, NGTCP2_PKT_0RTT, flags, + ts); +} + +/* + * conn_handshake_probe_left returns nonzero if there are probe + * packets to be sent for Initial or Handshake packet number space + * left. + */ +static int conn_handshake_probe_left(ngtcp2_conn *conn) { + return (conn->in_pktns && conn->in_pktns->rtb.probe_pkt_left) || + conn->hs_pktns->rtb.probe_pkt_left; +} + +/* + * conn_validate_early_transport_params_limits validates that the + * limits in transport parameters remembered by client for early data + * are not reduced. This function is only used by client and should + * only be called when early data is accepted by server. + */ +static int conn_validate_early_transport_params_limits(ngtcp2_conn *conn) { + const ngtcp2_transport_params *params = conn->remote.transport_params; + + assert(!conn->server); + assert(params); + + if (conn->early.transport_params.active_connection_id_limit > + params->active_connection_id_limit || + conn->early.transport_params.initial_max_data > + params->initial_max_data || + conn->early.transport_params.initial_max_stream_data_bidi_local > + params->initial_max_stream_data_bidi_local || + conn->early.transport_params.initial_max_stream_data_bidi_remote > + params->initial_max_stream_data_bidi_remote || + conn->early.transport_params.initial_max_stream_data_uni > + params->initial_max_stream_data_uni || + conn->early.transport_params.initial_max_streams_bidi > + params->initial_max_streams_bidi || + conn->early.transport_params.initial_max_streams_uni > + params->initial_max_streams_uni || + conn->early.transport_params.max_datagram_frame_size > + params->max_datagram_frame_size) { + return NGTCP2_ERR_PROTO; + } + + return 0; +} + +/* + * conn_write_handshake writes QUIC handshake packets to the buffer + * pointed by |dest| of length |destlen|. |write_datalen| specifies + * the expected length of 0RTT or 1RTT packet payload. Specify 0 to + * |write_datalen| if there is no such data. + * + * This function returns the number of bytes written to the buffer, or + * one of the following negative error codes: + * + * NGTCP2_ERR_PKT_NUM_EXHAUSTED + * Packet number is exhausted. + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM + * Required transport parameter is missing. + * NGTCP2_CS_CLOSING + * Connection is in closing state. + * NGTCP2_CS_DRAINING + * Connection is in draining state. + * + * In addition to the above negative error codes, the same error codes + * from conn_recv_pkt may also be returned. + */ +static ngtcp2_ssize conn_write_handshake(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + uint64_t write_datalen, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_ssize res = 0, nwrite = 0, early_spktlen = 0; + size_t origlen = destlen; + uint64_t pending_early_datalen; + ngtcp2_dcid *dcid; + ngtcp2_preferred_addr *paddr; + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + pending_early_datalen = conn_retry_early_payloadlen(conn); + if (pending_early_datalen) { + write_datalen = pending_early_datalen; + } + + if (!(conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY)) { + nwrite = + conn_write_client_initial(conn, pi, dest, destlen, write_datalen, ts); + if (nwrite <= 0) { + return nwrite; + } + } else { + nwrite = conn_write_handshake_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, + NGTCP2_WRITE_PKT_FLAG_NONE, write_datalen, ts); + if (nwrite < 0) { + return nwrite; + } + } + + if (pending_early_datalen) { + early_spktlen = conn_retransmit_retry_early( + conn, pi, dest + nwrite, destlen - (size_t)nwrite, + nwrite ? NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING + : NGTCP2_WRITE_PKT_FLAG_NONE, + ts); + + if (early_spktlen < 0) { + assert(ngtcp2_err_is_fatal((int)early_spktlen)); + return early_spktlen; + } + } + + conn->state = NGTCP2_CS_CLIENT_WAIT_HANDSHAKE; + + res = nwrite + early_spktlen; + + return res; + case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE: + if (!conn_handshake_probe_left(conn) && conn_cwnd_is_zero(conn)) { + destlen = 0; + } else { + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED)) { + pending_early_datalen = conn_retry_early_payloadlen(conn); + if (pending_early_datalen) { + write_datalen = pending_early_datalen; + } + } + + nwrite = + conn_write_handshake_pkts(conn, pi, dest, destlen, write_datalen, ts); + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } + + if (!conn_is_handshake_completed(conn)) { + if (!(conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED)) { + nwrite = conn_retransmit_retry_early(conn, pi, dest, destlen, + NGTCP2_WRITE_PKT_FLAG_NONE, ts); + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + } + + if (res == 0) { + nwrite = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + if (nwrite < 0) { + return nwrite; + } + res = nwrite; + } + + return res; + } + + if (!(conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED)) { + return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM; + } + + if ((conn->flags & NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED) && + !(conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED)) { + rv = conn_validate_early_transport_params_limits(conn); + if (rv != 0) { + return rv; + } + } + + /* Server might increase stream data limits. Extend it if we have + streams created for early data. */ + rv = conn_sync_stream_data_limit(conn); + if (rv != 0) { + return rv; + } + + conn->state = NGTCP2_CS_POST_HANDSHAKE; + + assert(conn->remote.transport_params); + + if (conn->remote.transport_params->preferred_address_present) { + assert(!ngtcp2_ringbuf_full(&conn->dcid.unused.rb)); + + paddr = &conn->remote.transport_params->preferred_address; + dcid = ngtcp2_ringbuf_push_back(&conn->dcid.unused.rb); + ngtcp2_dcid_init(dcid, 1, &paddr->cid, paddr->stateless_reset_token); + + rv = ngtcp2_gaptr_push(&conn->dcid.seqgap, 1, 1); + if (rv != 0) { + return (ngtcp2_ssize)rv; + } + } + + if (conn->remote.transport_params->stateless_reset_token_present) { + assert(conn->dcid.current.seq == 0); + assert(!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT)); + ngtcp2_dcid_set_token( + &conn->dcid.current, + conn->remote.transport_params->stateless_reset_token); + } + + rv = conn_call_activate_dcid(conn, &conn->dcid.current); + if (rv != 0) { + return rv; + } + + conn_process_early_rtb(conn); + + if (!conn->local.settings.no_pmtud) { + rv = conn_start_pmtud(conn); + if (rv != 0) { + return rv; + } + } + + return res; + case NGTCP2_CS_SERVER_INITIAL: + nwrite = + conn_write_handshake_pkts(conn, pi, dest, destlen, write_datalen, ts); + if (nwrite < 0) { + return nwrite; + } + + if (nwrite) { + conn->state = NGTCP2_CS_SERVER_WAIT_HANDSHAKE; + } + + return nwrite; + case NGTCP2_CS_SERVER_WAIT_HANDSHAKE: + if (conn_handshake_probe_left(conn) || !conn_cwnd_is_zero(conn)) { + nwrite = + conn_write_handshake_pkts(conn, pi, dest, destlen, write_datalen, ts); + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } + + if (res == 0) { + nwrite = conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + dest += nwrite; + origlen -= (size_t)nwrite; + } + + return res; + case NGTCP2_CS_CLOSING: + return NGTCP2_ERR_CLOSING; + case NGTCP2_CS_DRAINING: + return NGTCP2_ERR_DRAINING; + default: + return 0; + } +} + +/** + * @function + * + * `conn_client_write_handshake` writes client side handshake data and + * 0RTT packet. + * + * In order to send STREAM data in 0RTT packet, specify + * |vmsg|->stream. |vmsg|->stream.strm, |vmsg|->stream.fin, + * |vmsg|->stream.data, and |vmsg|->stream.datacnt are stream to which + * 0-RTT data is sent, whether it is a last data chunk in this stream, + * a vector of 0-RTT data, and its number of elements respectively. + * The amount of 0RTT data sent is assigned to + * *|vmsg|->stream.pdatalen. If no data is sent, -1 is assigned. + * Note that 0 length STREAM frame is allowed in QUIC, so 0 might be + * assigned to *|vmsg|->stream.pdatalen. + * + * This function returns 0 if it cannot write any frame because buffer + * is too small, or packet is congestion limited. Application should + * keep reading and wait for congestion window to grow. + * + * This function returns the number of bytes written to the buffer + * pointed by |dest| if it succeeds, or one of the following negative + * error codes: (TBD). + */ +static ngtcp2_ssize conn_client_write_handshake(ngtcp2_conn *conn, + ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_vmsg *vmsg, + ngtcp2_tstamp ts) { + int send_stream = 0; + int send_datagram = 0; + ngtcp2_ssize spktlen, early_spktlen; + uint64_t datalen; + uint64_t write_datalen = 0; + uint8_t wflags = NGTCP2_WRITE_PKT_FLAG_NONE; + int ppe_pending = (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) != 0; + uint32_t version; + + assert(!conn->server); + + /* conn->early.ckm might be created in the first call of + conn_handshake(). Check it later. */ + if (vmsg) { + switch (vmsg->type) { + case NGTCP2_VMSG_TYPE_STREAM: + datalen = ngtcp2_vec_len(vmsg->stream.data, vmsg->stream.datacnt); + send_stream = + conn_retry_early_payloadlen(conn) == 0 && + /* 0 length STREAM frame is allowed */ + (datalen == 0 || + (datalen > 0 && + (vmsg->stream.strm->tx.max_offset - vmsg->stream.strm->tx.offset) && + (conn->tx.max_offset - conn->tx.offset))); + if (send_stream) { + write_datalen = + conn_enforce_flow_control(conn, vmsg->stream.strm, datalen); + write_datalen = + ngtcp2_min(write_datalen, NGTCP2_MIN_COALESCED_PAYLOADLEN); + write_datalen += NGTCP2_STREAM_OVERHEAD; + + if (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_MORE) { + wflags |= NGTCP2_WRITE_PKT_FLAG_MORE; + } + } else { + vmsg = NULL; + } + break; + case NGTCP2_VMSG_TYPE_DATAGRAM: + datalen = ngtcp2_vec_len(vmsg->datagram.data, vmsg->datagram.datacnt); + send_datagram = conn_retry_early_payloadlen(conn) == 0; + if (send_datagram) { + write_datalen = datalen + NGTCP2_DATAGRAM_OVERHEAD; + + if (vmsg->datagram.flags & NGTCP2_WRITE_DATAGRAM_FLAG_MORE) { + wflags |= NGTCP2_WRITE_PKT_FLAG_MORE; + } + } else { + vmsg = NULL; + } + break; + } + } + + if (!ppe_pending) { + spktlen = conn_write_handshake(conn, pi, dest, destlen, write_datalen, ts); + + if (spktlen < 0) { + return spktlen; + } + + if ((conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) || + !conn->early.ckm || (!send_stream && !send_datagram)) { + return spktlen; + } + + /* If spktlen > 0, we are making a compound packet. If Initial + packet is written, we have to pad bytes to 0-RTT packet. */ + version = conn->negotiated_version ? conn->negotiated_version + : conn->client_chosen_version; + if (spktlen > 0 && + ngtcp2_pkt_get_type_long(version, dest[0]) == NGTCP2_PKT_INITIAL) { + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + conn->pkt.require_padding = 1; + } else { + conn->pkt.require_padding = 0; + } + } else { + assert(!conn->pktns.crypto.rx.ckm); + assert(!conn->pktns.crypto.tx.ckm); + assert(conn->early.ckm); + + if (conn->pkt.require_padding) { + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + spktlen = conn->pkt.hs_spktlen; + } + + dest += spktlen; + destlen -= (size_t)spktlen; + + if (conn_cwnd_is_zero(conn)) { + return spktlen; + } + + early_spktlen = conn_write_pkt(conn, pi, dest, destlen, vmsg, NGTCP2_PKT_0RTT, + wflags, ts); + + if (early_spktlen < 0) { + switch (early_spktlen) { + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + return spktlen; + case NGTCP2_ERR_WRITE_MORE: + conn->pkt.hs_spktlen = spktlen; + break; + } + return early_spktlen; + } + + return spktlen + early_spktlen; +} + +void ngtcp2_conn_handshake_completed(ngtcp2_conn *conn) { + conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED; + if (conn->server) { + conn->flags |= NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED; + } +} + +int ngtcp2_conn_get_handshake_completed(ngtcp2_conn *conn) { + return conn_is_handshake_completed(conn) && + (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED); +} + +int ngtcp2_conn_sched_ack(ngtcp2_conn *conn, ngtcp2_acktr *acktr, + int64_t pkt_num, int active_ack, ngtcp2_tstamp ts) { + int rv; + (void)conn; + + rv = ngtcp2_acktr_add(acktr, pkt_num, active_ack, ts); + if (rv != 0) { + assert(rv != NGTCP2_ERR_INVALID_ARGUMENT); + return rv; + } + + return 0; +} + +int ngtcp2_accept(ngtcp2_pkt_hd *dest, const uint8_t *pkt, size_t pktlen) { + ngtcp2_ssize nread; + ngtcp2_pkt_hd hd, *p; + + if (dest) { + p = dest; + } else { + p = &hd; + } + + if (pktlen == 0 || (pkt[0] & NGTCP2_HEADER_FORM_BIT) == 0) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + nread = ngtcp2_pkt_decode_hd_long(p, pkt, pktlen); + if (nread < 0) { + return (int)nread; + } + + switch (p->type) { + case NGTCP2_PKT_INITIAL: + break; + case NGTCP2_PKT_0RTT: + /* 0-RTT packet may arrive before Initial packet due to + re-ordering. ngtcp2 does not buffer 0RTT packet unless the + very first Initial packet is received or token is received. */ + return NGTCP2_ERR_RETRY; + default: + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (pktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE || + (p->token.len == 0 && p->dcid.datalen < NGTCP2_MIN_INITIAL_DCIDLEN)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + return 0; +} + +int ngtcp2_conn_install_initial_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *rx_aead_ctx, + const uint8_t *rx_iv, const ngtcp2_crypto_cipher_ctx *rx_hp_ctx, + const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv, + const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen) { + ngtcp2_pktns *pktns = conn->in_pktns; + int rv; + + assert(ivlen >= 8); + assert(pktns); + + conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.rx.hp_ctx); + pktns->crypto.rx.hp_ctx.native_handle = NULL; + + if (pktns->crypto.rx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.rx.ckm->aead_ctx); + ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem); + pktns->crypto.rx.ckm = NULL; + } + + conn_call_delete_crypto_cipher_ctx(conn, &pktns->crypto.tx.hp_ctx); + pktns->crypto.tx.hp_ctx.native_handle = NULL; + + if (pktns->crypto.tx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &pktns->crypto.tx.ckm->aead_ctx); + ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem); + pktns->crypto.tx.ckm = NULL; + } + + rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, NULL, rx_iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, NULL, tx_iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + /* Take owner ship after we are sure that no failure occurs, so that + caller can delete these contexts on failure. */ + pktns->crypto.rx.ckm->aead_ctx = *rx_aead_ctx; + pktns->crypto.rx.hp_ctx = *rx_hp_ctx; + pktns->crypto.tx.ckm->aead_ctx = *tx_aead_ctx; + pktns->crypto.tx.hp_ctx = *tx_hp_ctx; + + return 0; +} + +int ngtcp2_conn_install_vneg_initial_key( + ngtcp2_conn *conn, uint32_t version, + const ngtcp2_crypto_aead_ctx *rx_aead_ctx, const uint8_t *rx_iv, + const ngtcp2_crypto_cipher_ctx *rx_hp_ctx, + const ngtcp2_crypto_aead_ctx *tx_aead_ctx, const uint8_t *tx_iv, + const ngtcp2_crypto_cipher_ctx *tx_hp_ctx, size_t ivlen) { + int rv; + + assert(ivlen >= 8); + + conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.rx.hp_ctx); + conn->vneg.rx.hp_ctx.native_handle = NULL; + + if (conn->vneg.rx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.rx.ckm->aead_ctx); + ngtcp2_crypto_km_del(conn->vneg.rx.ckm, conn->mem); + conn->vneg.rx.ckm = NULL; + } + + conn_call_delete_crypto_cipher_ctx(conn, &conn->vneg.tx.hp_ctx); + conn->vneg.tx.hp_ctx.native_handle = NULL; + + if (conn->vneg.tx.ckm) { + conn_call_delete_crypto_aead_ctx(conn, &conn->vneg.tx.ckm->aead_ctx); + ngtcp2_crypto_km_del(conn->vneg.tx.ckm, conn->mem); + conn->vneg.tx.ckm = NULL; + } + + rv = ngtcp2_crypto_km_new(&conn->vneg.rx.ckm, NULL, 0, NULL, rx_iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_crypto_km_new(&conn->vneg.tx.ckm, NULL, 0, NULL, tx_iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + /* Take owner ship after we are sure that no failure occurs, so that + caller can delete these contexts on failure. */ + conn->vneg.rx.ckm->aead_ctx = *rx_aead_ctx; + conn->vneg.rx.hp_ctx = *rx_hp_ctx; + conn->vneg.tx.ckm->aead_ctx = *tx_aead_ctx; + conn->vneg.tx.hp_ctx = *tx_hp_ctx; + conn->vneg.version = version; + + return 0; +} + +int ngtcp2_conn_install_rx_handshake_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pktns *pktns = conn->hs_pktns; + int rv; + + assert(ivlen >= 8); + assert(pktns); + assert(!pktns->crypto.rx.hp_ctx.native_handle); + assert(!pktns->crypto.rx.ckm); + + rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, NULL, 0, aead_ctx, iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + pktns->crypto.rx.hp_ctx = *hp_ctx; + + rv = conn_call_recv_rx_key(conn, NGTCP2_CRYPTO_LEVEL_HANDSHAKE); + if (rv != 0) { + ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem); + pktns->crypto.rx.ckm = NULL; + + memset(&pktns->crypto.rx.hp_ctx, 0, sizeof(pktns->crypto.rx.hp_ctx)); + + return rv; + } + + return 0; +} + +int ngtcp2_conn_install_tx_handshake_key( + ngtcp2_conn *conn, const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pktns *pktns = conn->hs_pktns; + int rv; + + assert(ivlen >= 8); + assert(pktns); + assert(!pktns->crypto.tx.hp_ctx.native_handle); + assert(!pktns->crypto.tx.ckm); + + rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, NULL, 0, aead_ctx, iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + pktns->crypto.tx.hp_ctx = *hp_ctx; + + if (conn->server) { + rv = ngtcp2_conn_commit_local_transport_params(conn); + if (rv != 0) { + return rv; + } + } + + rv = conn_call_recv_tx_key(conn, NGTCP2_CRYPTO_LEVEL_HANDSHAKE); + if (rv != 0) { + ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem); + pktns->crypto.tx.ckm = NULL; + + memset(&pktns->crypto.tx.hp_ctx, 0, sizeof(pktns->crypto.tx.hp_ctx)); + + return rv; + } + + return 0; +} + +int ngtcp2_conn_install_early_key(ngtcp2_conn *conn, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx) { + int rv; + + assert(ivlen >= 8); + assert(!conn->early.hp_ctx.native_handle); + assert(!conn->early.ckm); + + rv = ngtcp2_crypto_km_new(&conn->early.ckm, NULL, 0, aead_ctx, iv, ivlen, + conn->mem); + if (rv != 0) { + return rv; + } + + conn->early.hp_ctx = *hp_ctx; + + conn->flags |= NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED; + + if (conn->server) { + rv = conn_call_recv_rx_key(conn, NGTCP2_CRYPTO_LEVEL_EARLY); + } else { + rv = conn_call_recv_tx_key(conn, NGTCP2_CRYPTO_LEVEL_EARLY); + } + if (rv != 0) { + ngtcp2_crypto_km_del(conn->early.ckm, conn->mem); + conn->early.ckm = NULL; + + memset(&conn->early.hp_ctx, 0, sizeof(conn->early.hp_ctx)); + + return rv; + } + + return 0; +} + +int ngtcp2_conn_install_rx_key(ngtcp2_conn *conn, const uint8_t *secret, + size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pktns *pktns = &conn->pktns; + int rv; + + assert(ivlen >= 8); + assert(!pktns->crypto.rx.hp_ctx.native_handle); + assert(!pktns->crypto.rx.ckm); + + rv = ngtcp2_crypto_km_new(&pktns->crypto.rx.ckm, secret, secretlen, aead_ctx, + iv, ivlen, conn->mem); + if (rv != 0) { + return rv; + } + + pktns->crypto.rx.hp_ctx = *hp_ctx; + + if (!conn->server) { + if (conn->remote.pending_transport_params) { + ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem); + + conn->remote.transport_params = conn->remote.pending_transport_params; + conn->remote.pending_transport_params = NULL; + conn_sync_stream_id_limit(conn); + conn->tx.max_offset = conn->remote.transport_params->initial_max_data; + } + + if (conn->early.ckm) { + conn_discard_early_key(conn); + } + } + + rv = conn_call_recv_rx_key(conn, NGTCP2_CRYPTO_LEVEL_APPLICATION); + if (rv != 0) { + ngtcp2_crypto_km_del(pktns->crypto.rx.ckm, conn->mem); + pktns->crypto.rx.ckm = NULL; + + memset(&pktns->crypto.rx.hp_ctx, 0, sizeof(pktns->crypto.rx.hp_ctx)); + + return rv; + } + + return 0; +} + +int ngtcp2_conn_install_tx_key(ngtcp2_conn *conn, const uint8_t *secret, + size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pktns *pktns = &conn->pktns; + int rv; + + assert(ivlen >= 8); + assert(!pktns->crypto.tx.hp_ctx.native_handle); + assert(!pktns->crypto.tx.ckm); + + rv = ngtcp2_crypto_km_new(&pktns->crypto.tx.ckm, secret, secretlen, aead_ctx, + iv, ivlen, conn->mem); + if (rv != 0) { + return rv; + } + + pktns->crypto.tx.hp_ctx = *hp_ctx; + + if (conn->server) { + if (conn->remote.pending_transport_params) { + ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem); + + conn->remote.transport_params = conn->remote.pending_transport_params; + conn->remote.pending_transport_params = NULL; + conn_sync_stream_id_limit(conn); + conn->tx.max_offset = conn->remote.transport_params->initial_max_data; + } + } else if (conn->early.ckm) { + conn_discard_early_key(conn); + } + + rv = conn_call_recv_tx_key(conn, NGTCP2_CRYPTO_LEVEL_APPLICATION); + if (rv != 0) { + ngtcp2_crypto_km_del(pktns->crypto.tx.ckm, conn->mem); + pktns->crypto.tx.ckm = NULL; + + memset(&pktns->crypto.tx.hp_ctx, 0, sizeof(pktns->crypto.tx.hp_ctx)); + + return rv; + } + + return 0; +} + +int ngtcp2_conn_initiate_key_update(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_tstamp confirmed_ts = conn->crypto.key_update.confirmed_ts; + ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns); + + assert(conn->state == NGTCP2_CS_POST_HANDSHAKE); + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) || + (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) || + !conn->crypto.key_update.new_tx_ckm || + !conn->crypto.key_update.new_rx_ckm || + (confirmed_ts != UINT64_MAX && confirmed_ts + 3 * pto > ts)) { + return NGTCP2_ERR_INVALID_STATE; + } + + conn_rotate_keys(conn, NGTCP2_MAX_PKT_NUM, /* initiator = */ 1); + + return 0; +} + +/* + * conn_retire_stale_bound_dcid retires stale destination connection + * ID in conn->dcid.bound to keep some unused destination connection + * IDs available. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_retire_stale_bound_dcid(ngtcp2_conn *conn, + ngtcp2_duration timeout, + ngtcp2_tstamp ts) { + size_t i; + ngtcp2_dcid *dcid, *last; + int rv; + + for (i = 0; i < ngtcp2_ringbuf_len(&conn->dcid.bound.rb);) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + + assert(dcid->cid.datalen); + + if (dcid->bound_ts + timeout > ts) { + ++i; + continue; + } + + rv = conn_retire_dcid_seq(conn, dcid->seq); + if (rv != 0) { + return rv; + } + + if (i == 0) { + ngtcp2_ringbuf_pop_front(&conn->dcid.bound.rb); + continue; + } + + if (i == ngtcp2_ringbuf_len(&conn->dcid.bound.rb) - 1) { + ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); + break; + } + + last = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, + ngtcp2_ringbuf_len(&conn->dcid.bound.rb) - 1); + ngtcp2_dcid_copy(dcid, last); + ngtcp2_ringbuf_pop_back(&conn->dcid.bound.rb); + } + + return 0; +} + +ngtcp2_tstamp ngtcp2_conn_loss_detection_expiry(ngtcp2_conn *conn) { + return conn->cstat.loss_detection_timer; +} + +ngtcp2_tstamp ngtcp2_conn_internal_expiry(ngtcp2_conn *conn) { + ngtcp2_tstamp res = UINT64_MAX, t; + ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns); + ngtcp2_scid *scid; + ngtcp2_dcid *dcid; + size_t i, len; + + if (conn->pv) { + res = ngtcp2_pv_next_expiry(conn->pv); + } + + if (conn->pmtud) { + res = ngtcp2_min(res, conn->pmtud->expiry); + } + + if (!ngtcp2_pq_empty(&conn->scid.used)) { + scid = ngtcp2_struct_of(ngtcp2_pq_top(&conn->scid.used), ngtcp2_scid, pe); + if (scid->retired_ts != UINT64_MAX) { + t = scid->retired_ts + pto; + res = ngtcp2_min(res, t); + } + } + + if (ngtcp2_ringbuf_len(&conn->dcid.retired.rb)) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, 0); + t = dcid->retired_ts + pto; + res = ngtcp2_min(res, t); + } + + if (conn->dcid.current.cid.datalen) { + len = ngtcp2_ringbuf_len(&conn->dcid.bound.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, i); + + assert(dcid->cid.datalen); + assert(dcid->bound_ts != UINT64_MAX); + + t = dcid->bound_ts + 3 * pto; + res = ngtcp2_min(res, t); + } + } + + if (conn->server && conn->early.ckm && + conn->early.discard_started_ts != UINT64_MAX) { + t = conn->early.discard_started_ts + 3 * pto; + res = ngtcp2_min(res, t); + } + + return res; +} + +ngtcp2_tstamp ngtcp2_conn_ack_delay_expiry(ngtcp2_conn *conn) { + ngtcp2_acktr *acktr = &conn->pktns.acktr; + + if (!(acktr->flags & NGTCP2_ACKTR_FLAG_CANCEL_TIMER) && + acktr->first_unacked_ts != UINT64_MAX) { + return acktr->first_unacked_ts + conn_compute_ack_delay(conn); + } + return UINT64_MAX; +} + +static ngtcp2_tstamp conn_handshake_expiry(ngtcp2_conn *conn) { + if (conn_is_handshake_completed(conn) || + conn->local.settings.handshake_timeout == UINT64_MAX) { + return UINT64_MAX; + } + + return conn->local.settings.initial_ts + + conn->local.settings.handshake_timeout; +} + +ngtcp2_tstamp ngtcp2_conn_get_expiry(ngtcp2_conn *conn) { + ngtcp2_tstamp t1 = ngtcp2_conn_loss_detection_expiry(conn); + ngtcp2_tstamp t2 = ngtcp2_conn_ack_delay_expiry(conn); + ngtcp2_tstamp t3 = ngtcp2_conn_internal_expiry(conn); + ngtcp2_tstamp t4 = ngtcp2_conn_lost_pkt_expiry(conn); + ngtcp2_tstamp t5 = conn_keep_alive_expiry(conn); + ngtcp2_tstamp t6 = conn_handshake_expiry(conn); + ngtcp2_tstamp t7 = ngtcp2_conn_get_idle_expiry(conn); + ngtcp2_tstamp res = ngtcp2_min(t1, t2); + res = ngtcp2_min(res, t3); + res = ngtcp2_min(res, t4); + res = ngtcp2_min(res, t5); + res = ngtcp2_min(res, t6); + res = ngtcp2_min(res, t7); + return ngtcp2_min(res, conn->tx.pacing.next_ts); +} + +int ngtcp2_conn_handle_expiry(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + int rv; + ngtcp2_duration pto = conn_compute_pto(conn, &conn->pktns); + + assert(!(conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING)); + + if (ngtcp2_conn_get_idle_expiry(conn) <= ts) { + return NGTCP2_ERR_IDLE_CLOSE; + } + + ngtcp2_conn_cancel_expired_ack_delay_timer(conn, ts); + + conn_cancel_expired_keep_alive_timer(conn, ts); + + conn_cancel_expired_pkt_tx_timer(conn, ts); + + ngtcp2_conn_remove_lost_pkt(conn, ts); + + if (conn->pv) { + ngtcp2_pv_cancel_expired_timer(conn->pv, ts); + } + + if (conn->pmtud) { + ngtcp2_pmtud_handle_expiry(conn->pmtud, ts); + if (ngtcp2_pmtud_finished(conn->pmtud)) { + ngtcp2_conn_stop_pmtud(conn); + } + } + + if (ngtcp2_conn_loss_detection_expiry(conn) <= ts) { + rv = ngtcp2_conn_on_loss_detection_timer(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (conn->dcid.current.cid.datalen) { + rv = conn_retire_stale_bound_dcid(conn, 3 * pto, ts); + if (rv != 0) { + return rv; + } + } + + rv = conn_remove_retired_connection_id(conn, pto, ts); + if (rv != 0) { + return rv; + } + + if (conn->server && conn->early.ckm && + conn->early.discard_started_ts != UINT64_MAX) { + if (conn->early.discard_started_ts + 3 * pto <= ts) { + conn_discard_early_key(conn); + } + } + + if (!conn_is_handshake_completed(conn) && + conn->local.settings.handshake_timeout != UINT64_MAX && + conn->local.settings.initial_ts + + conn->local.settings.handshake_timeout <= + ts) { + return NGTCP2_ERR_HANDSHAKE_TIMEOUT; + } + + return 0; +} + +static void acktr_cancel_expired_ack_delay_timer(ngtcp2_acktr *acktr, + ngtcp2_duration max_ack_delay, + ngtcp2_tstamp ts) { + if (!(acktr->flags & NGTCP2_ACKTR_FLAG_CANCEL_TIMER) && + acktr->first_unacked_ts != UINT64_MAX && + acktr->first_unacked_ts + max_ack_delay <= ts) { + acktr->flags |= NGTCP2_ACKTR_FLAG_CANCEL_TIMER; + } +} + +void ngtcp2_conn_cancel_expired_ack_delay_timer(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + ngtcp2_duration ack_delay = conn_compute_ack_delay(conn); + + if (conn->in_pktns) { + acktr_cancel_expired_ack_delay_timer(&conn->in_pktns->acktr, 0, ts); + } + if (conn->hs_pktns) { + acktr_cancel_expired_ack_delay_timer(&conn->hs_pktns->acktr, 0, ts); + } + acktr_cancel_expired_ack_delay_timer(&conn->pktns.acktr, ack_delay, ts); +} + +ngtcp2_tstamp ngtcp2_conn_lost_pkt_expiry(ngtcp2_conn *conn) { + ngtcp2_tstamp res = UINT64_MAX, ts; + + if (conn->in_pktns) { + ts = ngtcp2_rtb_lost_pkt_ts(&conn->in_pktns->rtb); + if (ts != UINT64_MAX) { + ts += conn_compute_pto(conn, conn->in_pktns); + res = ngtcp2_min(res, ts); + } + } + + if (conn->hs_pktns) { + ts = ngtcp2_rtb_lost_pkt_ts(&conn->hs_pktns->rtb); + if (ts != UINT64_MAX) { + ts += conn_compute_pto(conn, conn->hs_pktns); + res = ngtcp2_min(res, ts); + } + } + + ts = ngtcp2_rtb_lost_pkt_ts(&conn->pktns.rtb); + if (ts != UINT64_MAX) { + ts += conn_compute_pto(conn, &conn->pktns); + res = ngtcp2_min(res, ts); + } + + return res; +} + +void ngtcp2_conn_remove_lost_pkt(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_duration pto; + + if (conn->in_pktns) { + pto = conn_compute_pto(conn, conn->in_pktns); + ngtcp2_rtb_remove_expired_lost_pkt(&conn->in_pktns->rtb, pto, ts); + } + if (conn->hs_pktns) { + pto = conn_compute_pto(conn, conn->hs_pktns); + ngtcp2_rtb_remove_expired_lost_pkt(&conn->hs_pktns->rtb, pto, ts); + } + pto = conn_compute_pto(conn, &conn->pktns); + ngtcp2_rtb_remove_expired_lost_pkt(&conn->pktns.rtb, pto, ts); +} + +/* + * select_preferred_version selects the most preferred version. + * |fallback_version| is chosen if no preference is made, or + * |preferred_versions| does not include any of |chosen_version| or + * |other_versions|. |chosen_version| is treated as an extra other + * version. + */ +static uint32_t select_preferred_version(const uint32_t *preferred_versions, + size_t preferred_versionslen, + uint32_t chosen_version, + const uint8_t *other_versions, + size_t other_versionslen, + uint32_t fallback_version) { + size_t i, j; + const uint8_t *p; + uint32_t v; + + if (!preferred_versionslen || + (!other_versionslen && chosen_version == fallback_version)) { + return fallback_version; + } + + for (i = 0; i < preferred_versionslen; ++i) { + if (preferred_versions[i] == chosen_version) { + return chosen_version; + } + for (j = 0, p = other_versions; j < other_versionslen; + j += sizeof(uint32_t)) { + p = ngtcp2_get_uint32(&v, p); + + if (preferred_versions[i] == v) { + return v; + } + } + } + + return fallback_version; +} + +/* + * conn_client_validate_transport_params validates |params| as client. + * |params| must be sent with Encrypted Extensions. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_TRANSPORT_PARAM + * params contains preferred address but server chose zero-length + * connection ID. + * NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE + * Validation against version negotiation parameters failed. + */ +static int +conn_client_validate_transport_params(ngtcp2_conn *conn, + const ngtcp2_transport_params *params) { + if (!ngtcp2_cid_eq(&conn->rcid, ¶ms->original_dcid)) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) { + if (!params->retry_scid_present) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + if (!ngtcp2_cid_eq(&conn->retry_scid, ¶ms->retry_scid)) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + } else if (params->retry_scid_present) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (params->preferred_address_present && + conn->dcid.current.cid.datalen == 0) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (params->version_info_present) { + if (conn->negotiated_version != params->version_info.chosen_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + assert(vneg_other_versions_includes(conn->vneg.other_versions, + conn->vneg.other_versionslen, + conn->negotiated_version)); + } else if (conn->client_chosen_version != conn->negotiated_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + /* When client reacted upon Version Negotiation */ + if (conn->local.settings.original_version != conn->client_chosen_version) { + if (!params->version_info_present) { + assert(conn->client_chosen_version == conn->negotiated_version); + + /* QUIC v1 (and the supported draft versions) are treated + specially. If version_info is missing, no further validation + is necessary. + https://datatracker.ietf.org/doc/html/draft-ietf-quic-version-negotiation-10#section-8 + */ + if (conn->client_chosen_version == NGTCP2_PROTO_VER_V1 || + (NGTCP2_PROTO_VER_DRAFT_MIN <= conn->client_chosen_version && + conn->client_chosen_version <= NGTCP2_PROTO_VER_DRAFT_MAX)) { + return 0; + } + + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + /* Server choose original version after Version Negotiation. + Draft does not say this particular case, but this smells like + misbehaved server because server should accept original_version + in the original connection. */ + if (conn->local.settings.original_version == + params->version_info.chosen_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + /* Check version downgrade on incompatible version negotiation. */ + if (params->version_info.other_versionslen == 0) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + if (conn->client_chosen_version != + select_preferred_version(conn->vneg.preferred_versions, + conn->vneg.preferred_versionslen, + params->version_info.chosen_version, + params->version_info.other_versions, + params->version_info.other_versionslen, + /* fallback_version = */ 0)) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + } + + return 0; +} + +uint32_t +ngtcp2_conn_server_negotiate_version(ngtcp2_conn *conn, + const ngtcp2_version_info *version_info) { + assert(conn->server); + assert(conn->client_chosen_version == version_info->chosen_version); + + return select_preferred_version( + conn->vneg.preferred_versions, conn->vneg.preferred_versionslen, + version_info->chosen_version, version_info->other_versions, + version_info->other_versionslen, version_info->chosen_version); +} + +int ngtcp2_conn_set_remote_transport_params( + ngtcp2_conn *conn, const ngtcp2_transport_params *params) { + int rv; + + /* We expect this function is called once per QUIC connection, but + GnuTLS server seems to call TLS extension callback twice if it + sends HelloRetryRequest. In practice, same QUIC transport + parameters are sent in the 2nd client flight, just returning 0 + would cause no harm. */ + if (conn->flags & NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED) { + return 0; + } + + /* Assume that ngtcp2_decode_transport_params sets default value if + active_connection_id_limit is omitted. */ + if (params->active_connection_id_limit < + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + /* We assume that conn->dcid.current.cid is still the initial one. + This requires that transport parameter must be fed into + ngtcp2_conn as early as possible. */ + if (!ngtcp2_cid_eq(&conn->dcid.current.cid, ¶ms->initial_scid)) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (params->max_udp_payload_size < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + return NGTCP2_ERR_TRANSPORT_PARAM; + } + + if (conn->server) { + if (params->version_info_present) { + if (params->version_info.chosen_version != conn->client_chosen_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE; + } + + conn->negotiated_version = + ngtcp2_conn_server_negotiate_version(conn, ¶ms->version_info); + if (conn->negotiated_version != conn->client_chosen_version) { + rv = conn_call_version_negotiation(conn, conn->negotiated_version, + &conn->rcid); + if (rv != 0) { + return rv; + } + } + } else { + conn->negotiated_version = conn->client_chosen_version; + } + + conn->local.transport_params.version_info.chosen_version = + conn->negotiated_version; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "the negotiated version is 0x%08x", + conn->negotiated_version); + } else { + rv = conn_client_validate_transport_params(conn, params); + if (rv != 0) { + return rv; + } + } + + ngtcp2_log_remote_tp(&conn->log, + conn->server + ? NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO + : NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, + params); + + ngtcp2_qlog_parameters_set_transport_params(&conn->qlog, params, conn->server, + NGTCP2_QLOG_SIDE_REMOTE); + + if ((conn->server && conn->pktns.crypto.tx.ckm) || + (!conn->server && conn->pktns.crypto.rx.ckm)) { + ngtcp2_transport_params_del(conn->remote.transport_params, conn->mem); + conn->remote.transport_params = NULL; + + rv = ngtcp2_transport_params_copy_new(&conn->remote.transport_params, + params, conn->mem); + if (rv != 0) { + return rv; + } + conn_sync_stream_id_limit(conn); + conn->tx.max_offset = conn->remote.transport_params->initial_max_data; + } else { + assert(!conn->remote.pending_transport_params); + + rv = ngtcp2_transport_params_copy_new( + &conn->remote.pending_transport_params, params, conn->mem); + if (rv != 0) { + return rv; + } + } + + conn->flags |= NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED; + + return 0; +} + +int ngtcp2_conn_decode_remote_transport_params(ngtcp2_conn *conn, + const uint8_t *data, + size_t datalen) { + ngtcp2_transport_params params; + int rv; + + rv = ngtcp2_decode_transport_params( + ¶ms, + conn->server ? NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO + : NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, + data, datalen); + if (rv != 0) { + return rv; + } + + return ngtcp2_conn_set_remote_transport_params(conn, ¶ms); +} + +const ngtcp2_transport_params * +ngtcp2_conn_get_remote_transport_params(ngtcp2_conn *conn) { + if (conn->remote.pending_transport_params) { + return conn->remote.pending_transport_params; + } + + return conn->remote.transport_params; +} + +void ngtcp2_conn_set_early_remote_transport_params_versioned( + ngtcp2_conn *conn, int transport_params_version, + const ngtcp2_transport_params *params) { + ngtcp2_transport_params *p; + (void)transport_params_version; + + assert(!conn->server); + assert(!conn->remote.transport_params); + + /* Assume that all pointer fields in p are NULL */ + p = ngtcp2_mem_calloc(conn->mem, 1, sizeof(*p)); + + conn->remote.transport_params = p; + + p->initial_max_streams_bidi = params->initial_max_streams_bidi; + p->initial_max_streams_uni = params->initial_max_streams_uni; + p->initial_max_stream_data_bidi_local = + params->initial_max_stream_data_bidi_local; + p->initial_max_stream_data_bidi_remote = + params->initial_max_stream_data_bidi_remote; + p->initial_max_stream_data_uni = params->initial_max_stream_data_uni; + p->initial_max_data = params->initial_max_data; + p->active_connection_id_limit = + ngtcp2_max(NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT, + params->active_connection_id_limit); + p->max_idle_timeout = params->max_idle_timeout; + if (!params->max_udp_payload_size) { + p->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + } else { + p->max_udp_payload_size = + ngtcp2_max(NGTCP2_MAX_UDP_PAYLOAD_SIZE, params->max_udp_payload_size); + } + p->disable_active_migration = params->disable_active_migration; + p->max_datagram_frame_size = params->max_datagram_frame_size; + + /* These parameters are treated specially. If server accepts early + data, it must not set values for these parameters that are + smaller than these remembered values. */ + conn->early.transport_params.initial_max_streams_bidi = + params->initial_max_streams_bidi; + conn->early.transport_params.initial_max_streams_uni = + params->initial_max_streams_uni; + conn->early.transport_params.initial_max_stream_data_bidi_local = + params->initial_max_stream_data_bidi_local; + conn->early.transport_params.initial_max_stream_data_bidi_remote = + params->initial_max_stream_data_bidi_remote; + conn->early.transport_params.initial_max_stream_data_uni = + params->initial_max_stream_data_uni; + conn->early.transport_params.initial_max_data = params->initial_max_data; + conn->early.transport_params.active_connection_id_limit = + params->active_connection_id_limit; + conn->early.transport_params.max_datagram_frame_size = + params->max_datagram_frame_size; + + conn_sync_stream_id_limit(conn); + + conn->tx.max_offset = p->initial_max_data; + + ngtcp2_qlog_parameters_set_transport_params(&conn->qlog, p, conn->server, + NGTCP2_QLOG_SIDE_REMOTE); +} + +int ngtcp2_conn_set_local_transport_params_versioned( + ngtcp2_conn *conn, int transport_params_version, + const ngtcp2_transport_params *params) { + (void)transport_params_version; + + assert(conn->server); + assert(params->active_connection_id_limit <= NGTCP2_MAX_DCID_POOL_SIZE); + + if (conn->hs_pktns == NULL || conn->hs_pktns->crypto.tx.ckm) { + return NGTCP2_ERR_INVALID_STATE; + } + + conn_set_local_transport_params(conn, params); + + return 0; +} + +int ngtcp2_conn_commit_local_transport_params(ngtcp2_conn *conn) { + const ngtcp2_mem *mem = conn->mem; + ngtcp2_transport_params *params = &conn->local.transport_params; + ngtcp2_scid *scident; + int rv; + + assert(1 == ngtcp2_ksl_len(&conn->scid.set)); + + if (params->active_connection_id_limit == 0) { + params->active_connection_id_limit = + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + } + + params->initial_scid = conn->oscid; + + if (conn->oscid.datalen == 0) { + params->preferred_address_present = 0; + } + + if (conn->server && params->preferred_address_present) { + scident = ngtcp2_mem_malloc(mem, sizeof(*scident)); + if (scident == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_scid_init(scident, 1, ¶ms->preferred_address.cid); + + rv = ngtcp2_ksl_insert(&conn->scid.set, NULL, &scident->cid, scident); + if (rv != 0) { + ngtcp2_mem_free(mem, scident); + return rv; + } + + conn->scid.last_seq = 1; + } + + conn->rx.window = conn->rx.unsent_max_offset = conn->rx.max_offset = + params->initial_max_data; + conn->remote.bidi.unsent_max_streams = params->initial_max_streams_bidi; + conn->remote.bidi.max_streams = params->initial_max_streams_bidi; + conn->remote.uni.unsent_max_streams = params->initial_max_streams_uni; + conn->remote.uni.max_streams = params->initial_max_streams_uni; + + conn->flags |= NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED; + + ngtcp2_qlog_parameters_set_transport_params(&conn->qlog, params, conn->server, + NGTCP2_QLOG_SIDE_LOCAL); + + return 0; +} + +const ngtcp2_transport_params * +ngtcp2_conn_get_local_transport_params(ngtcp2_conn *conn) { + return &conn->local.transport_params; +} + +ngtcp2_ssize ngtcp2_conn_encode_local_transport_params(ngtcp2_conn *conn, + uint8_t *dest, + size_t destlen) { + return ngtcp2_encode_transport_params( + dest, destlen, + conn->server ? NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS + : NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, + &conn->local.transport_params); +} + +int ngtcp2_conn_open_bidi_stream(ngtcp2_conn *conn, int64_t *pstream_id, + void *stream_user_data) { + int rv; + ngtcp2_strm *strm; + + if (ngtcp2_conn_get_streams_bidi_left(conn) == 0) { + return NGTCP2_ERR_STREAM_ID_BLOCKED; + } + + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rv = ngtcp2_conn_init_stream(conn, strm, conn->local.bidi.next_stream_id, + stream_user_data); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + + *pstream_id = conn->local.bidi.next_stream_id; + conn->local.bidi.next_stream_id += 4; + + return 0; +} + +int ngtcp2_conn_open_uni_stream(ngtcp2_conn *conn, int64_t *pstream_id, + void *stream_user_data) { + int rv; + ngtcp2_strm *strm; + + if (ngtcp2_conn_get_streams_uni_left(conn) == 0) { + return NGTCP2_ERR_STREAM_ID_BLOCKED; + } + + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + if (strm == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rv = ngtcp2_conn_init_stream(conn, strm, conn->local.uni.next_stream_id, + stream_user_data); + if (rv != 0) { + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + return rv; + } + ngtcp2_strm_shutdown(strm, NGTCP2_STRM_FLAG_SHUT_RD); + + *pstream_id = conn->local.uni.next_stream_id; + conn->local.uni.next_stream_id += 4; + + return 0; +} + +ngtcp2_strm *ngtcp2_conn_find_stream(ngtcp2_conn *conn, int64_t stream_id) { + return ngtcp2_map_find(&conn->strms, (uint64_t)stream_id); +} + +ngtcp2_ssize ngtcp2_conn_write_stream_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen, + uint32_t flags, int64_t stream_id, const uint8_t *data, size_t datalen, + ngtcp2_tstamp ts) { + ngtcp2_vec datav; + + datav.len = datalen; + datav.base = (uint8_t *)data; + + return ngtcp2_conn_writev_stream_versioned(conn, path, pkt_info_version, pi, + dest, destlen, pdatalen, flags, + stream_id, &datav, 1, ts); +} + +static ngtcp2_ssize conn_write_vmsg_wrapper(ngtcp2_conn *conn, + ngtcp2_path *path, + int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, ngtcp2_vmsg *vmsg, + ngtcp2_tstamp ts) { + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_ssize nwrite; + int undersized; + + nwrite = ngtcp2_conn_write_vmsg(conn, path, pkt_info_version, pi, dest, + destlen, vmsg, ts); + if (nwrite < 0) { + return nwrite; + } + + if (cstat->bytes_in_flight >= cstat->cwnd) { + conn->rst.is_cwnd_limited = 1; + } + + if (vmsg == NULL && cstat->bytes_in_flight < cstat->cwnd && + conn->tx.strmq_nretrans == 0) { + if (conn->local.settings.no_tx_udp_payload_size_shaping) { + undersized = + (size_t)nwrite < conn->local.settings.max_tx_udp_payload_size; + } else { + undersized = (size_t)nwrite < conn->dcid.current.max_udp_payload_size; + } + + if (undersized) { + conn->rst.app_limited = conn->rst.delivered + cstat->bytes_in_flight; + + if (conn->rst.app_limited == 0) { + conn->rst.app_limited = cstat->max_tx_udp_payload_size; + } + } + } + + return nwrite; +} + +ngtcp2_ssize ngtcp2_conn_writev_stream_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, ngtcp2_ssize *pdatalen, + uint32_t flags, int64_t stream_id, const ngtcp2_vec *datav, size_t datavcnt, + ngtcp2_tstamp ts) { + ngtcp2_vmsg vmsg, *pvmsg; + ngtcp2_strm *strm; + int64_t datalen; + + if (pdatalen) { + *pdatalen = -1; + } + + if (stream_id != -1) { + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return NGTCP2_ERR_STREAM_NOT_FOUND; + } + + if (strm->flags & NGTCP2_STRM_FLAG_SHUT_WR) { + return NGTCP2_ERR_STREAM_SHUT_WR; + } + + datalen = ngtcp2_vec_len_varint(datav, datavcnt); + if (datalen == -1) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if ((uint64_t)datalen > NGTCP2_MAX_VARINT - strm->tx.offset || + (uint64_t)datalen > NGTCP2_MAX_VARINT - conn->tx.offset) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + vmsg.type = NGTCP2_VMSG_TYPE_STREAM; + vmsg.stream.strm = strm; + vmsg.stream.flags = flags; + vmsg.stream.data = datav; + vmsg.stream.datacnt = datavcnt; + vmsg.stream.pdatalen = pdatalen; + + pvmsg = &vmsg; + } else { + pvmsg = NULL; + } + + return conn_write_vmsg_wrapper(conn, path, pkt_info_version, pi, dest, + destlen, pvmsg, ts); +} + +ngtcp2_ssize ngtcp2_conn_writev_datagram_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, int *paccepted, + uint32_t flags, uint64_t dgram_id, const ngtcp2_vec *datav, size_t datavcnt, + ngtcp2_tstamp ts) { + ngtcp2_vmsg vmsg; + int64_t datalen; + + if (paccepted) { + *paccepted = 0; + } + + if (conn->remote.transport_params == NULL || + conn->remote.transport_params->max_datagram_frame_size == 0) { + return NGTCP2_ERR_INVALID_STATE; + } + + datalen = ngtcp2_vec_len_varint(datav, datavcnt); + if (datalen == -1 +#if UINT64_MAX > SIZE_MAX + || (uint64_t)datalen > SIZE_MAX +#endif /* UINT64_MAX > SIZE_MAX */ + ) { + return NGTCP2_ERR_INVALID_STATE; + } + + if (conn->remote.transport_params->max_datagram_frame_size < + ngtcp2_pkt_datagram_framelen((size_t)datalen)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + vmsg.type = NGTCP2_VMSG_TYPE_DATAGRAM; + vmsg.datagram.dgram_id = dgram_id; + vmsg.datagram.flags = flags; + vmsg.datagram.data = datav; + vmsg.datagram.datacnt = datavcnt; + vmsg.datagram.paccepted = paccepted; + + return conn_write_vmsg_wrapper(conn, path, pkt_info_version, pi, dest, + destlen, &vmsg, ts); +} + +ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, + int pkt_info_version, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_vmsg *vmsg, ngtcp2_tstamp ts) { + ngtcp2_ssize nwrite; + size_t origlen; + size_t origdestlen = destlen; + int rv; + uint8_t wflags = NGTCP2_WRITE_PKT_FLAG_NONE; + int ppe_pending = (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) != 0; + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_ssize res = 0; + uint64_t server_tx_left; + uint64_t datalen; + uint64_t write_datalen = 0; + int64_t prev_in_pkt_num = -1; + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *rtbent; + (void)pkt_info_version; + + conn->log.last_ts = ts; + conn->qlog.last_ts = ts; + + if (path) { + ngtcp2_path_copy(path, &conn->dcid.current.ps.path); + } + + origlen = destlen = + conn_shape_udp_payload(conn, &conn->dcid.current, destlen); + + if (!ppe_pending && pi) { + pi->ecn = NGTCP2_ECN_NOT_ECT; + } + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + case NGTCP2_CS_CLIENT_WAIT_HANDSHAKE: + case NGTCP2_CS_CLIENT_TLS_HANDSHAKE_FAILED: + if (!conn_pacing_pkt_tx_allowed(conn, ts)) { + assert(!ppe_pending); + + return conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + } + + nwrite = conn_client_write_handshake(conn, pi, dest, destlen, vmsg, ts); + /* We might be unable to write a packet because of depletion of + congestion window budget, perhaps due to packet loss that + shrinks the window drastically. */ + if (nwrite <= 0) { + return nwrite; + } + if (conn->state != NGTCP2_CS_POST_HANDSHAKE) { + return nwrite; + } + + assert(nwrite); + assert(dest[0] & NGTCP2_HEADER_FORM_BIT); + assert(conn->negotiated_version); + + if (ngtcp2_pkt_get_type_long(conn->negotiated_version, dest[0]) == + NGTCP2_PKT_INITIAL) { + /* We have added padding already, but in that case, there is no + space left to write 1RTT packet. */ + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + + res = nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + /* Break here so that we can coalesces 1RTT packet. */ + break; + case NGTCP2_CS_SERVER_INITIAL: + case NGTCP2_CS_SERVER_WAIT_HANDSHAKE: + case NGTCP2_CS_SERVER_TLS_HANDSHAKE_FAILED: + if (!conn_pacing_pkt_tx_allowed(conn, ts)) { + assert(!ppe_pending); + + if (!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + if (server_tx_left == 0) { + return 0; + } + + origlen = (size_t)ngtcp2_min((uint64_t)origlen, server_tx_left); + } + + return conn_write_handshake_ack_pkts(conn, pi, dest, origlen, ts); + } + + if (!ppe_pending) { + if (!(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + if (server_tx_left == 0) { + if (cstat->loss_detection_timer != UINT64_MAX) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_RCV, + "loss detection timer canceled due to amplification limit"); + cstat->loss_detection_timer = UINT64_MAX; + } + + return 0; + } + + destlen = (size_t)ngtcp2_min((uint64_t)destlen, server_tx_left); + } + + if (vmsg) { + switch (vmsg->type) { + case NGTCP2_VMSG_TYPE_STREAM: + datalen = ngtcp2_vec_len(vmsg->stream.data, vmsg->stream.datacnt); + if (datalen == 0 || (datalen > 0 && + (vmsg->stream.strm->tx.max_offset - + vmsg->stream.strm->tx.offset) && + (conn->tx.max_offset - conn->tx.offset))) { + write_datalen = + conn_enforce_flow_control(conn, vmsg->stream.strm, datalen); + write_datalen = + ngtcp2_min(write_datalen, NGTCP2_MIN_COALESCED_PAYLOADLEN); + write_datalen += NGTCP2_STREAM_OVERHEAD; + } + break; + case NGTCP2_VMSG_TYPE_DATAGRAM: + write_datalen = + ngtcp2_vec_len(vmsg->datagram.data, vmsg->datagram.datacnt) + + NGTCP2_DATAGRAM_OVERHEAD; + break; + default: + ngtcp2_unreachable(); + } + + if (conn->in_pktns && write_datalen > 0) { + it = ngtcp2_rtb_head(&conn->in_pktns->rtb); + if (!ngtcp2_ksl_it_end(&it)) { + rtbent = ngtcp2_ksl_it_get(&it); + prev_in_pkt_num = rtbent->hd.pkt_num; + } + } + } + + nwrite = conn_write_handshake(conn, pi, dest, destlen, write_datalen, ts); + if (nwrite < 0) { + return nwrite; + } + + res = nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + + if (conn->in_pktns && write_datalen > 0) { + it = ngtcp2_rtb_head(&conn->in_pktns->rtb); + if (!ngtcp2_ksl_it_end(&it)) { + rtbent = ngtcp2_ksl_it_get(&it); + if (rtbent->hd.pkt_num != prev_in_pkt_num && + (rtbent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + /* We have added padding already, but in that case, there + is no space left to write 1RTT packet. */ + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + } + } + } + if (conn->state != NGTCP2_CS_POST_HANDSHAKE && + conn->pktns.crypto.tx.ckm == NULL) { + return res; + } + break; + case NGTCP2_CS_POST_HANDSHAKE: + if (!conn_pacing_pkt_tx_allowed(conn, ts)) { + assert(!ppe_pending); + + if (conn->server && + !(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + if (server_tx_left == 0) { + return 0; + } + + origlen = (size_t)ngtcp2_min((uint64_t)origlen, server_tx_left); + } + + return conn_write_ack_pkt(conn, pi, dest, origlen, NGTCP2_PKT_1RTT, ts); + } + + break; + case NGTCP2_CS_CLOSING: + return NGTCP2_ERR_CLOSING; + case NGTCP2_CS_DRAINING: + return NGTCP2_ERR_DRAINING; + default: + return 0; + } + + assert(conn->pktns.crypto.tx.ckm); + + if (conn_check_pkt_num_exhausted(conn)) { + return NGTCP2_ERR_PKT_NUM_EXHAUSTED; + } + + if (vmsg) { + switch (vmsg->type) { + case NGTCP2_VMSG_TYPE_STREAM: + if (vmsg->stream.flags & NGTCP2_WRITE_STREAM_FLAG_MORE) { + wflags |= NGTCP2_WRITE_PKT_FLAG_MORE; + } + break; + case NGTCP2_VMSG_TYPE_DATAGRAM: + if (vmsg->datagram.flags & NGTCP2_WRITE_DATAGRAM_FLAG_MORE) { + wflags |= NGTCP2_WRITE_PKT_FLAG_MORE; + } + break; + default: + break; + } + } + + if (ppe_pending) { + res = conn->pkt.hs_spktlen; + conn->pkt.hs_spktlen = 0; + if (conn->pkt.require_padding) { + wflags |= NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + /* dest and destlen have already been adjusted in ppe in the first + run. They are adjusted for probe packet later. */ + nwrite = conn_write_pkt(conn, pi, dest, destlen, vmsg, NGTCP2_PKT_1RTT, + wflags, ts); + goto fin; + } else { + conn->pkt.require_padding = + (wflags & NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING); + + if (conn->state == NGTCP2_CS_POST_HANDSHAKE) { + rv = conn_prepare_key_update(conn, ts); + if (rv != 0) { + return rv; + } + } + + if (!conn->pktns.rtb.probe_pkt_left && conn_cwnd_is_zero(conn)) { + destlen = 0; + } else { + if (res == 0) { + nwrite = + conn_write_path_response(conn, path, pi, dest, origdestlen, ts); + if (nwrite) { + goto fin; + } + + if (conn->pv) { + nwrite = + conn_write_path_challenge(conn, path, pi, dest, origdestlen, ts); + if (nwrite) { + goto fin; + } + } + + if (conn->pmtud && + (conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED) && + (!conn->hs_pktns || + ngtcp2_ksl_len(&conn->hs_pktns->crypto.tx.frq) == 0)) { + nwrite = conn_write_pmtud_probe(conn, pi, dest, origdestlen, ts); + if (nwrite) { + goto fin; + } + } + } + } + + if (conn->server && + !(conn->dcid.current.flags & NGTCP2_DCID_FLAG_PATH_VALIDATED)) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + origlen = (size_t)ngtcp2_min((uint64_t)origlen, server_tx_left); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, server_tx_left); + + if (server_tx_left == 0 && + conn->cstat.loss_detection_timer != UINT64_MAX) { + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_RCV, + "loss detection timer canceled due to amplification limit"); + conn->cstat.loss_detection_timer = UINT64_MAX; + } + } + } + + if (res == 0) { + if (conn_handshake_remnants_left(conn)) { + if (conn_handshake_probe_left(conn)) { + destlen = origlen; + } + nwrite = conn_write_handshake_pkts(conn, pi, dest, destlen, + /* write_datalen = */ 0, ts); + if (nwrite < 0) { + return nwrite; + } + if (nwrite > 0) { + res = nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } + } + } + + if (conn->pktns.rtb.probe_pkt_left) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "transmit probe pkt left=%zu", + conn->pktns.rtb.probe_pkt_left); + + nwrite = conn_write_pkt(conn, pi, dest, destlen, vmsg, NGTCP2_PKT_1RTT, + wflags, ts); + + goto fin; + } + + nwrite = conn_write_pkt(conn, pi, dest, destlen, vmsg, NGTCP2_PKT_1RTT, + wflags, ts); + if (nwrite) { + assert(nwrite != NGTCP2_ERR_NOBUF); + goto fin; + } + + if (res == 0) { + nwrite = conn_write_ack_pkt(conn, pi, dest, origlen, NGTCP2_PKT_1RTT, ts); + } + +fin: + conn->pkt.hs_spktlen = 0; + + if (nwrite >= 0) { + res += nwrite; + return res; + } + /* NGTCP2_CONN_FLAG_PPE_PENDING is set in conn_write_pkt above. + ppe_pending cannot be used here. */ + if (conn->flags & NGTCP2_CONN_FLAG_PPE_PENDING) { + conn->pkt.hs_spktlen = res; + } + + return nwrite; +} + +static ngtcp2_ssize +conn_write_connection_close(ngtcp2_conn *conn, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, uint8_t pkt_type, + uint64_t error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_ssize res = 0, nwrite; + ngtcp2_frame fr; + uint8_t flags = NGTCP2_WRITE_PKT_FLAG_NONE; + + fr.type = NGTCP2_FRAME_CONNECTION_CLOSE; + fr.connection_close.error_code = error_code; + fr.connection_close.frame_type = 0; + fr.connection_close.reasonlen = reasonlen; + fr.connection_close.reason = (uint8_t *)reason; + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) && + pkt_type != NGTCP2_PKT_INITIAL) { + if (in_pktns && conn->server) { + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_INITIAL, + NGTCP2_WRITE_PKT_FLAG_NONE, &conn->dcid.current.cid, &fr, + NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + if (nwrite < 0) { + return nwrite; + } + + dest += nwrite; + destlen -= (size_t)nwrite; + res += nwrite; + } + + if (pkt_type != NGTCP2_PKT_HANDSHAKE && hs_pktns && + hs_pktns->crypto.tx.ckm) { + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_HANDSHAKE, + NGTCP2_WRITE_PKT_FLAG_NONE, &conn->dcid.current.cid, &fr, + NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + if (nwrite < 0) { + return nwrite; + } + + dest += nwrite; + destlen -= (size_t)nwrite; + res += nwrite; + } + } + + if (!conn->server && pkt_type == NGTCP2_PKT_INITIAL) { + flags = NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING; + } + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, pkt_type, flags, &conn->dcid.current.cid, &fr, + NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + + if (res == 0) { + return NGTCP2_ERR_NOBUF; + } + + return res; +} + +ngtcp2_ssize ngtcp2_conn_write_connection_close_pkt( + ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint64_t error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + uint8_t pkt_type; + ngtcp2_ssize nwrite; + uint64_t server_tx_left; + + conn->log.last_ts = ts; + conn->qlog.last_ts = ts; + + if (conn_check_pkt_num_exhausted(conn)) { + return NGTCP2_ERR_PKT_NUM_EXHAUSTED; + } + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + return NGTCP2_ERR_INVALID_STATE; + case NGTCP2_CS_CLOSING: + case NGTCP2_CS_DRAINING: + return 0; + default: + break; + } + + if (path) { + ngtcp2_path_copy(path, &conn->dcid.current.ps.path); + } + + destlen = conn_shape_udp_payload(conn, &conn->dcid.current, destlen); + + if (pi) { + pi->ecn = NGTCP2_ECN_NOT_ECT; + } + + if (conn->server) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, server_tx_left); + } + + if (conn->state == NGTCP2_CS_POST_HANDSHAKE || + (conn->server && conn->pktns.crypto.tx.ckm)) { + pkt_type = NGTCP2_PKT_1RTT; + } else if (hs_pktns && hs_pktns->crypto.tx.ckm) { + pkt_type = NGTCP2_PKT_HANDSHAKE; + } else if (in_pktns && in_pktns->crypto.tx.ckm) { + pkt_type = NGTCP2_PKT_INITIAL; + } else { + /* This branch is taken if server has not read any Initial packet + from client. */ + return NGTCP2_ERR_INVALID_STATE; + } + + nwrite = conn_write_connection_close(conn, pi, dest, destlen, pkt_type, + error_code, reason, reasonlen, ts); + if (nwrite < 0) { + return nwrite; + } + + conn->state = NGTCP2_CS_CLOSING; + + return nwrite; +} + +ngtcp2_ssize ngtcp2_conn_write_application_close_pkt( + ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint64_t app_error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts) { + ngtcp2_ssize nwrite; + ngtcp2_ssize res = 0; + ngtcp2_frame fr; + uint64_t server_tx_left; + + conn->log.last_ts = ts; + conn->qlog.last_ts = ts; + + if (conn_check_pkt_num_exhausted(conn)) { + return NGTCP2_ERR_PKT_NUM_EXHAUSTED; + } + + switch (conn->state) { + case NGTCP2_CS_CLIENT_INITIAL: + return NGTCP2_ERR_INVALID_STATE; + case NGTCP2_CS_CLOSING: + case NGTCP2_CS_DRAINING: + return 0; + default: + break; + } + + if (path) { + ngtcp2_path_copy(path, &conn->dcid.current.ps.path); + } + + destlen = conn_shape_udp_payload(conn, &conn->dcid.current, destlen); + + if (pi) { + pi->ecn = NGTCP2_ECN_NOT_ECT; + } + + if (conn->server) { + server_tx_left = conn_server_tx_left(conn, &conn->dcid.current); + destlen = (size_t)ngtcp2_min((uint64_t)destlen, server_tx_left); + } + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) { + nwrite = conn_write_connection_close(conn, pi, dest, destlen, + conn->hs_pktns->crypto.tx.ckm + ? NGTCP2_PKT_HANDSHAKE + : NGTCP2_PKT_INITIAL, + NGTCP2_APPLICATION_ERROR, NULL, 0, ts); + if (nwrite < 0) { + return nwrite; + } + res = nwrite; + dest += nwrite; + destlen -= (size_t)nwrite; + } + + if (conn->state != NGTCP2_CS_POST_HANDSHAKE) { + assert(res); + + if (!conn->server || !conn->pktns.crypto.tx.ckm) { + return res; + } + } + + assert(conn->pktns.crypto.tx.ckm); + + fr.type = NGTCP2_FRAME_CONNECTION_CLOSE_APP; + fr.connection_close.error_code = app_error_code; + fr.connection_close.frame_type = 0; + fr.connection_close.reasonlen = reasonlen; + fr.connection_close.reason = (uint8_t *)reason; + + nwrite = ngtcp2_conn_write_single_frame_pkt( + conn, pi, dest, destlen, NGTCP2_PKT_1RTT, NGTCP2_WRITE_PKT_FLAG_NONE, + &conn->dcid.current.cid, &fr, NGTCP2_RTB_ENTRY_FLAG_NONE, NULL, ts); + + if (nwrite < 0) { + return nwrite; + } + + res += nwrite; + + if (res == 0) { + return NGTCP2_ERR_NOBUF; + } + + conn->state = NGTCP2_CS_CLOSING; + + return res; +} + +static void +connection_close_error_init(ngtcp2_connection_close_error *ccerr, + ngtcp2_connection_close_error_code_type type, + uint64_t error_code, const uint8_t *reason, + size_t reasonlen) { + ccerr->type = type; + ccerr->error_code = error_code; + ccerr->frame_type = 0; + ccerr->reason = (uint8_t *)reason; + ccerr->reasonlen = reasonlen; +} + +void ngtcp2_connection_close_error_default( + ngtcp2_connection_close_error *ccerr) { + connection_close_error_init(ccerr, + NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT, + NGTCP2_NO_ERROR, NULL, 0); +} + +void ngtcp2_connection_close_error_set_transport_error( + ngtcp2_connection_close_error *ccerr, uint64_t error_code, + const uint8_t *reason, size_t reasonlen) { + connection_close_error_init(ccerr, + NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT, + error_code, reason, reasonlen); +} + +void ngtcp2_connection_close_error_set_transport_error_liberr( + ngtcp2_connection_close_error *ccerr, int liberr, const uint8_t *reason, + size_t reasonlen) { + switch (liberr) { + case NGTCP2_ERR_RECV_VERSION_NEGOTIATION: + connection_close_error_init( + ccerr, + NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_VERSION_NEGOTIATION, + NGTCP2_NO_ERROR, reason, reasonlen); + + return; + case NGTCP2_ERR_IDLE_CLOSE: + connection_close_error_init( + ccerr, NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE, + NGTCP2_NO_ERROR, reason, reasonlen); + + return; + }; + + ngtcp2_connection_close_error_set_transport_error( + ccerr, ngtcp2_err_infer_quic_transport_error_code(liberr), reason, + reasonlen); +} + +void ngtcp2_connection_close_error_set_transport_error_tls_alert( + ngtcp2_connection_close_error *ccerr, uint8_t tls_alert, + const uint8_t *reason, size_t reasonlen) { + ngtcp2_connection_close_error_set_transport_error( + ccerr, NGTCP2_CRYPTO_ERROR | tls_alert, reason, reasonlen); +} + +void ngtcp2_connection_close_error_set_application_error( + ngtcp2_connection_close_error *ccerr, uint64_t error_code, + const uint8_t *reason, size_t reasonlen) { + connection_close_error_init( + ccerr, NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION, error_code, + reason, reasonlen); +} + +ngtcp2_ssize ngtcp2_conn_write_connection_close_versioned( + ngtcp2_conn *conn, ngtcp2_path *path, int pkt_info_version, + ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, + const ngtcp2_connection_close_error *ccerr, ngtcp2_tstamp ts) { + (void)pkt_info_version; + + switch (ccerr->type) { + case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT: + return ngtcp2_conn_write_connection_close_pkt( + conn, path, pi, dest, destlen, ccerr->error_code, ccerr->reason, + ccerr->reasonlen, ts); + case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION: + return ngtcp2_conn_write_application_close_pkt( + conn, path, pi, dest, destlen, ccerr->error_code, ccerr->reason, + ccerr->reasonlen, ts); + default: + return 0; + } +} + +int ngtcp2_conn_is_in_closing_period(ngtcp2_conn *conn) { + return conn->state == NGTCP2_CS_CLOSING; +} + +int ngtcp2_conn_is_in_draining_period(ngtcp2_conn *conn) { + return conn->state == NGTCP2_CS_DRAINING; +} + +int ngtcp2_conn_close_stream(ngtcp2_conn *conn, ngtcp2_strm *strm) { + int rv; + + rv = conn_call_stream_close(conn, strm); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_map_remove(&conn->strms, (ngtcp2_map_key_type)strm->stream_id); + if (rv != 0) { + assert(rv != NGTCP2_ERR_INVALID_ARGUMENT); + return rv; + } + + if (ngtcp2_strm_is_tx_queued(strm)) { + ngtcp2_pq_remove(&conn->tx.strmq, &strm->pe); + if (!ngtcp2_strm_streamfrq_empty(strm)) { + assert(conn->tx.strmq_nretrans); + --conn->tx.strmq_nretrans; + } + } + + ngtcp2_strm_free(strm); + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, strm); + + return 0; +} + +int ngtcp2_conn_close_stream_if_shut_rdwr(ngtcp2_conn *conn, + ngtcp2_strm *strm) { + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RDWR) == + NGTCP2_STRM_FLAG_SHUT_RDWR && + ((strm->flags & NGTCP2_STRM_FLAG_RECV_RST) || + ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset) && + (((strm->flags & NGTCP2_STRM_FLAG_SENT_RST) && + (strm->flags & NGTCP2_STRM_FLAG_RST_ACKED)) || + ngtcp2_strm_is_all_tx_data_fin_acked(strm))) { + return ngtcp2_conn_close_stream(conn, strm); + } + return 0; +} + +/* + * conn_shutdown_stream_write closes send stream with error code + * |app_error_code|. RESET_STREAM frame is scheduled. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_shutdown_stream_write(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t app_error_code) { + ngtcp2_strm_set_app_error_code(strm, app_error_code); + + if ((strm->flags & NGTCP2_STRM_FLAG_SENT_RST) || + ngtcp2_strm_is_all_tx_data_fin_acked(strm)) { + return 0; + } + + /* Set this flag so that we don't accidentally send DATA to this + stream. */ + strm->flags |= NGTCP2_STRM_FLAG_SHUT_WR | NGTCP2_STRM_FLAG_SENT_RST; + + ngtcp2_strm_streamfrq_clear(strm); + + return conn_reset_stream(conn, strm, app_error_code); +} + +/* + * conn_shutdown_stream_read closes read stream with error code + * |app_error_code|. STOP_SENDING frame is scheduled. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_shutdown_stream_read(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t app_error_code) { + ngtcp2_strm_set_app_error_code(strm, app_error_code); + + if (strm->flags & NGTCP2_STRM_FLAG_STOP_SENDING) { + return 0; + } + if ((strm->flags & NGTCP2_STRM_FLAG_SHUT_RD) && + ngtcp2_strm_rx_offset(strm) == strm->rx.last_offset) { + return 0; + } + + /* Extend connection flow control window for the amount of data + which are not passed to application. */ + if (!(strm->flags & + (NGTCP2_STRM_FLAG_STOP_SENDING | NGTCP2_STRM_FLAG_RECV_RST))) { + ngtcp2_conn_extend_max_offset(conn, strm->rx.last_offset - + ngtcp2_strm_rx_offset(strm)); + } + + strm->flags |= NGTCP2_STRM_FLAG_STOP_SENDING; + + return conn_stop_sending(conn, strm, app_error_code); +} + +int ngtcp2_conn_shutdown_stream(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code) { + int rv; + ngtcp2_strm *strm; + + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return 0; + } + + if (bidi_stream(stream_id) || !conn_local_stream(conn, stream_id)) { + rv = conn_shutdown_stream_read(conn, strm, app_error_code); + if (rv != 0) { + return rv; + } + } + + if (bidi_stream(stream_id) || conn_local_stream(conn, stream_id)) { + rv = conn_shutdown_stream_write(conn, strm, app_error_code); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +int ngtcp2_conn_shutdown_stream_write(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code) { + ngtcp2_strm *strm; + + if (!bidi_stream(stream_id) && !conn_local_stream(conn, stream_id)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return 0; + } + + return conn_shutdown_stream_write(conn, strm, app_error_code); +} + +int ngtcp2_conn_shutdown_stream_read(ngtcp2_conn *conn, int64_t stream_id, + uint64_t app_error_code) { + ngtcp2_strm *strm; + + if (!bidi_stream(stream_id) && conn_local_stream(conn, stream_id)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return 0; + } + + return conn_shutdown_stream_read(conn, strm, app_error_code); +} + +/* + * conn_extend_max_stream_offset extends stream level flow control + * window by |datalen| of the stream denoted by |strm|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int conn_extend_max_stream_offset(ngtcp2_conn *conn, ngtcp2_strm *strm, + uint64_t datalen) { + ngtcp2_strm *top; + + if (datalen > NGTCP2_MAX_VARINT || + strm->rx.unsent_max_offset > NGTCP2_MAX_VARINT - datalen) { + strm->rx.unsent_max_offset = NGTCP2_MAX_VARINT; + } else { + strm->rx.unsent_max_offset += datalen; + } + + if (!(strm->flags & + (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_STOP_SENDING)) && + !ngtcp2_strm_is_tx_queued(strm) && + conn_should_send_max_stream_data(conn, strm)) { + if (!ngtcp2_pq_empty(&conn->tx.strmq)) { + top = ngtcp2_conn_tx_strmq_top(conn); + strm->cycle = top->cycle; + } + strm->cycle = conn_tx_strmq_first_cycle(conn); + return ngtcp2_conn_tx_strmq_push(conn, strm); + } + + return 0; +} + +int ngtcp2_conn_extend_max_stream_offset(ngtcp2_conn *conn, int64_t stream_id, + uint64_t datalen) { + ngtcp2_strm *strm; + + strm = ngtcp2_conn_find_stream(conn, stream_id); + if (strm == NULL) { + return 0; + } + + return conn_extend_max_stream_offset(conn, strm, datalen); +} + +void ngtcp2_conn_extend_max_offset(ngtcp2_conn *conn, uint64_t datalen) { + if (NGTCP2_MAX_VARINT < datalen || + conn->rx.unsent_max_offset > NGTCP2_MAX_VARINT - datalen) { + conn->rx.unsent_max_offset = NGTCP2_MAX_VARINT; + return; + } + + conn->rx.unsent_max_offset += datalen; +} + +void ngtcp2_conn_extend_max_streams_bidi(ngtcp2_conn *conn, size_t n) { + handle_max_remote_streams_extension(&conn->remote.bidi.unsent_max_streams, n); +} + +void ngtcp2_conn_extend_max_streams_uni(ngtcp2_conn *conn, size_t n) { + handle_max_remote_streams_extension(&conn->remote.uni.unsent_max_streams, n); +} + +const ngtcp2_cid *ngtcp2_conn_get_dcid(ngtcp2_conn *conn) { + return &conn->dcid.current.cid; +} + +const ngtcp2_cid *ngtcp2_conn_get_client_initial_dcid(ngtcp2_conn *conn) { + return &conn->rcid; +} + +uint32_t ngtcp2_conn_get_client_chosen_version(ngtcp2_conn *conn) { + return conn->client_chosen_version; +} + +uint32_t ngtcp2_conn_get_negotiated_version(ngtcp2_conn *conn) { + return conn->negotiated_version; +} + +static int delete_strms_pq_each(void *data, void *ptr) { + ngtcp2_conn *conn = ptr; + ngtcp2_strm *s = data; + + if (ngtcp2_strm_is_tx_queued(s)) { + ngtcp2_pq_remove(&conn->tx.strmq, &s->pe); + if (!ngtcp2_strm_streamfrq_empty(s)) { + assert(conn->tx.strmq_nretrans); + --conn->tx.strmq_nretrans; + } + } + + ngtcp2_strm_free(s); + ngtcp2_objalloc_strm_release(&conn->strm_objalloc, s); + + return 0; +} + +/* + * conn_discard_early_data_state discards any connection states which + * are altered by any operations during early data transfer. + */ +static void conn_discard_early_data_state(ngtcp2_conn *conn) { + ngtcp2_frame_chain **pfrc, *frc; + + ngtcp2_rtb_remove_early_data(&conn->pktns.rtb, &conn->cstat); + + ngtcp2_map_each_free(&conn->strms, delete_strms_pq_each, conn); + ngtcp2_map_clear(&conn->strms); + + conn->tx.offset = 0; + + conn->rx.unsent_max_offset = conn->rx.max_offset = + conn->local.transport_params.initial_max_data; + + conn->remote.bidi.unsent_max_streams = conn->remote.bidi.max_streams = + conn->local.transport_params.initial_max_streams_bidi; + + conn->remote.uni.unsent_max_streams = conn->remote.uni.max_streams = + conn->local.transport_params.initial_max_streams_uni; + + if (conn->server) { + conn->local.bidi.next_stream_id = 1; + conn->local.uni.next_stream_id = 3; + } else { + conn->local.bidi.next_stream_id = 0; + conn->local.uni.next_stream_id = 2; + } + + for (pfrc = &conn->pktns.tx.frq; *pfrc;) { + frc = *pfrc; + *pfrc = (*pfrc)->next; + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + } +} + +int ngtcp2_conn_early_data_rejected(ngtcp2_conn *conn) { + if (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) { + return 0; + } + + conn->flags |= NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED; + + conn_discard_early_data_state(conn); + + if (conn->callbacks.early_data_rejected) { + return conn->callbacks.early_data_rejected(conn, conn->user_data); + } + + return 0; +} + +int ngtcp2_conn_get_early_data_rejected(ngtcp2_conn *conn) { + return (conn->flags & NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED) != 0; +} + +int ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt, + ngtcp2_duration ack_delay, ngtcp2_tstamp ts) { + ngtcp2_conn_stat *cstat = &conn->cstat; + + if (cstat->min_rtt == UINT64_MAX) { + cstat->latest_rtt = rtt; + cstat->min_rtt = rtt; + cstat->smoothed_rtt = rtt; + cstat->rttvar = rtt / 2; + cstat->first_rtt_sample_ts = ts; + } else { + if (conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) { + assert(conn->remote.transport_params); + + ack_delay = + ngtcp2_min(ack_delay, conn->remote.transport_params->max_ack_delay); + } else if (ack_delay > 0 && rtt >= cstat->min_rtt && + rtt < cstat->min_rtt + ack_delay) { + /* Ignore RTT sample if adjusting ack_delay causes the sample + less than min_rtt before handshake confirmation. */ + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_RCV, + "ignore rtt sample because ack_delay is too large latest_rtt=%" PRIu64 + " min_rtt=%" PRIu64 " ack_delay=%" PRIu64, + rtt / NGTCP2_MILLISECONDS, cstat->min_rtt / NGTCP2_MILLISECONDS, + ack_delay / NGTCP2_MILLISECONDS); + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + cstat->latest_rtt = rtt; + cstat->min_rtt = ngtcp2_min(cstat->min_rtt, rtt); + + if (rtt >= cstat->min_rtt + ack_delay) { + rtt -= ack_delay; + } + + cstat->rttvar = (cstat->rttvar * 3 + (cstat->smoothed_rtt < rtt + ? rtt - cstat->smoothed_rtt + : cstat->smoothed_rtt - rtt)) / + 4; + cstat->smoothed_rtt = (cstat->smoothed_rtt * 7 + rtt) / 8; + } + + ngtcp2_log_info( + &conn->log, NGTCP2_LOG_EVENT_RCV, + "latest_rtt=%" PRIu64 " min_rtt=%" PRIu64 " smoothed_rtt=%" PRIu64 + " rttvar=%" PRIu64 " ack_delay=%" PRIu64, + cstat->latest_rtt / NGTCP2_MILLISECONDS, + cstat->min_rtt / NGTCP2_MILLISECONDS, + cstat->smoothed_rtt / NGTCP2_MILLISECONDS, + cstat->rttvar / NGTCP2_MILLISECONDS, ack_delay / NGTCP2_MILLISECONDS); + + return 0; +} + +void ngtcp2_conn_get_conn_stat_versioned(ngtcp2_conn *conn, + int conn_stat_version, + ngtcp2_conn_stat *cstat) { + (void)conn_stat_version; + + *cstat = conn->cstat; +} + +static void conn_get_loss_time_and_pktns(ngtcp2_conn *conn, + ngtcp2_tstamp *ploss_time, + ngtcp2_pktns **ppktns) { + ngtcp2_pktns *const ns[] = {conn->hs_pktns, &conn->pktns}; + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_duration *loss_time = cstat->loss_time + 1; + ngtcp2_tstamp earliest_loss_time = cstat->loss_time[NGTCP2_PKTNS_ID_INITIAL]; + ngtcp2_pktns *pktns = conn->in_pktns; + size_t i; + + for (i = 0; i < ngtcp2_arraylen(ns); ++i) { + if (ns[i] == NULL || loss_time[i] >= earliest_loss_time) { + continue; + } + + earliest_loss_time = loss_time[i]; + pktns = ns[i]; + } + + if (ploss_time) { + *ploss_time = earliest_loss_time; + } + if (ppktns) { + *ppktns = pktns; + } +} + +static ngtcp2_tstamp conn_get_earliest_pto_expiry(ngtcp2_conn *conn, + ngtcp2_tstamp ts) { + ngtcp2_pktns *ns[] = {conn->in_pktns, conn->hs_pktns, &conn->pktns}; + size_t i; + ngtcp2_tstamp earliest_ts = UINT64_MAX, t; + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_tstamp *times = cstat->last_tx_pkt_ts; + ngtcp2_duration duration = + compute_pto(cstat->smoothed_rtt, cstat->rttvar, /* max_ack_delay = */ 0) * + (1ULL << cstat->pto_count); + + for (i = NGTCP2_PKTNS_ID_INITIAL; i < NGTCP2_PKTNS_ID_MAX; ++i) { + if (ns[i] == NULL || ns[i]->rtb.num_pto_eliciting == 0 || + (times[i] == UINT64_MAX || + (i == NGTCP2_PKTNS_ID_APPLICATION && + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)))) { + continue; + } + + t = times[i] + duration; + + if (i == NGTCP2_PKTNS_ID_APPLICATION) { + assert(conn->remote.transport_params); + t += conn->remote.transport_params->max_ack_delay * + (1ULL << cstat->pto_count); + } + + if (t < earliest_ts) { + earliest_ts = t; + } + } + + if (earliest_ts == UINT64_MAX) { + return ts + duration; + } + + return earliest_ts; +} + +void ngtcp2_conn_set_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_conn_stat *cstat = &conn->cstat; + ngtcp2_duration timeout; + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_pktns *pktns = &conn->pktns; + ngtcp2_tstamp earliest_loss_time; + + conn_get_loss_time_and_pktns(conn, &earliest_loss_time, NULL); + + if (earliest_loss_time != UINT64_MAX) { + cstat->loss_detection_timer = earliest_loss_time; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV, + "loss_detection_timer=%" PRIu64 " nonzero crypto loss time", + cstat->loss_detection_timer); + return; + } + + if ((!in_pktns || in_pktns->rtb.num_pto_eliciting == 0) && + (!hs_pktns || hs_pktns->rtb.num_pto_eliciting == 0) && + (pktns->rtb.num_pto_eliciting == 0 || + !(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)) && + (conn->server || + (conn->flags & (NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED | + NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED)))) { + if (cstat->loss_detection_timer != UINT64_MAX) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV, + "loss detection timer canceled"); + cstat->loss_detection_timer = UINT64_MAX; + cstat->pto_count = 0; + } + return; + } + + cstat->loss_detection_timer = conn_get_earliest_pto_expiry(conn, ts); + + timeout = + cstat->loss_detection_timer > ts ? cstat->loss_detection_timer - ts : 0; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV, + "loss_detection_timer=%" PRIu64 " timeout=%" PRIu64, + cstat->loss_detection_timer, timeout / NGTCP2_MILLISECONDS); +} + +int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + ngtcp2_conn_stat *cstat = &conn->cstat; + int rv; + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_tstamp earliest_loss_time; + ngtcp2_pktns *loss_pktns = NULL; + + conn->log.last_ts = ts; + conn->qlog.last_ts = ts; + + switch (conn->state) { + case NGTCP2_CS_CLOSING: + case NGTCP2_CS_DRAINING: + cstat->loss_detection_timer = UINT64_MAX; + cstat->pto_count = 0; + return 0; + default: + break; + } + + if (cstat->loss_detection_timer == UINT64_MAX) { + return 0; + } + + conn_get_loss_time_and_pktns(conn, &earliest_loss_time, &loss_pktns); + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV, + "loss detection timer fired"); + + if (earliest_loss_time != UINT64_MAX) { + assert(loss_pktns); + + rv = ngtcp2_conn_detect_lost_pkt(conn, loss_pktns, cstat, ts); + if (rv != 0) { + return rv; + } + ngtcp2_conn_set_loss_detection_timer(conn, ts); + return 0; + } + + if (!conn->server && !conn_is_handshake_completed(conn)) { + if (hs_pktns->crypto.tx.ckm) { + hs_pktns->rtb.probe_pkt_left = 1; + } else { + in_pktns->rtb.probe_pkt_left = 1; + } + } else { + if (in_pktns && in_pktns->rtb.num_pto_eliciting) { + in_pktns->rtb.probe_pkt_left = 1; + + assert(hs_pktns); + + if (conn->server && hs_pktns->rtb.num_pto_eliciting) { + /* let server coalesce packets */ + hs_pktns->rtb.probe_pkt_left = 1; + } + } else if (hs_pktns && hs_pktns->rtb.num_pto_eliciting) { + hs_pktns->rtb.probe_pkt_left = 1; + } else { + conn->pktns.rtb.probe_pkt_left = 2; + } + } + + ++cstat->pto_count; + + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_RCV, "pto_count=%zu", + cstat->pto_count); + + ngtcp2_conn_set_loss_detection_timer(conn, ts); + + return 0; +} + +static int conn_buffer_crypto_data(ngtcp2_conn *conn, const uint8_t **pdata, + ngtcp2_pktns *pktns, const uint8_t *data, + size_t datalen) { + int rv; + ngtcp2_buf_chain **pbufchain = &pktns->crypto.tx.data; + + if (*pbufchain) { + for (; (*pbufchain)->next; pbufchain = &(*pbufchain)->next) + ; + + if (ngtcp2_buf_left(&(*pbufchain)->buf) < datalen) { + pbufchain = &(*pbufchain)->next; + } + } + + if (!*pbufchain) { + rv = ngtcp2_buf_chain_new(pbufchain, ngtcp2_max(1024, datalen), conn->mem); + if (rv != 0) { + return rv; + } + } + + *pdata = (*pbufchain)->buf.last; + (*pbufchain)->buf.last = ngtcp2_cpymem((*pbufchain)->buf.last, data, datalen); + + return 0; +} + +int ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + const uint8_t *data, const size_t datalen) { + ngtcp2_pktns *pktns; + ngtcp2_frame_chain *frc; + ngtcp2_crypto *fr; + int rv; + + if (datalen == 0) { + return 0; + } + + switch (crypto_level) { + case NGTCP2_CRYPTO_LEVEL_INITIAL: + assert(conn->in_pktns); + pktns = conn->in_pktns; + break; + case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: + assert(conn->hs_pktns); + pktns = conn->hs_pktns; + break; + case NGTCP2_CRYPTO_LEVEL_APPLICATION: + pktns = &conn->pktns; + break; + default: + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + rv = conn_buffer_crypto_data(conn, &data, pktns, data, datalen); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_frame_chain_objalloc_new(&frc, &conn->frc_objalloc); + if (rv != 0) { + return rv; + } + + fr = &frc->fr.crypto; + + fr->type = NGTCP2_FRAME_CRYPTO; + fr->offset = pktns->crypto.tx.offset; + fr->datacnt = 1; + fr->data[0].len = datalen; + fr->data[0].base = (uint8_t *)data; + + rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, &fr->offset, frc); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, &conn->frc_objalloc, conn->mem); + return rv; + } + + pktns->crypto.strm.tx.offset += datalen; + pktns->crypto.tx.offset += datalen; + + return 0; +} + +int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn, const uint8_t *token, + size_t tokenlen) { + int rv; + ngtcp2_frame_chain *nfrc; + ngtcp2_vec tokenv = {(uint8_t *)token, tokenlen}; + + assert(conn->server); + assert(token); + assert(tokenlen); + + rv = ngtcp2_frame_chain_new_token_objalloc_new( + &nfrc, &tokenv, &conn->frc_objalloc, conn->mem); + if (rv != 0) { + return rv; + } + + nfrc->next = conn->pktns.tx.frq; + conn->pktns.tx.frq = nfrc; + + return 0; +} + +ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn) { + assert(!ngtcp2_pq_empty(&conn->tx.strmq)); + return ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe); +} + +void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn) { + ngtcp2_strm *strm = ngtcp2_conn_tx_strmq_top(conn); + assert(strm); + ngtcp2_pq_pop(&conn->tx.strmq); + strm->pe.index = NGTCP2_PQ_BAD_INDEX; +} + +int ngtcp2_conn_tx_strmq_push(ngtcp2_conn *conn, ngtcp2_strm *strm) { + return ngtcp2_pq_push(&conn->tx.strmq, &strm->pe); +} + +static int conn_has_uncommited_preferred_address_cid(ngtcp2_conn *conn) { + return conn->server && + !(conn->flags & NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED) && + conn->oscid.datalen && + conn->local.transport_params.preferred_address_present; +} + +size_t ngtcp2_conn_get_num_scid(ngtcp2_conn *conn) { + return ngtcp2_ksl_len(&conn->scid.set) + + (size_t)conn_has_uncommited_preferred_address_cid(conn); +} + +size_t ngtcp2_conn_get_scid(ngtcp2_conn *conn, ngtcp2_cid *dest) { + ngtcp2_cid *origdest = dest; + ngtcp2_ksl_it it; + ngtcp2_scid *scid; + + for (it = ngtcp2_ksl_begin(&conn->scid.set); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + scid = ngtcp2_ksl_it_get(&it); + *dest++ = scid->cid; + } + + if (conn_has_uncommited_preferred_address_cid(conn)) { + *dest++ = conn->local.transport_params.preferred_address.cid; + } + + return (size_t)(dest - origdest); +} + +size_t ngtcp2_conn_get_num_active_dcid(ngtcp2_conn *conn) { + size_t n = 1; /* for conn->dcid.current */ + ngtcp2_pv *pv = conn->pv; + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED)) { + return 0; + } + + if (pv) { + if (pv->dcid.seq != conn->dcid.current.seq) { + ++n; + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.seq != conn->dcid.current.seq && + pv->fallback_dcid.seq != pv->dcid.seq) { + ++n; + } + } + + n += ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + + return n; +} + +static void copy_dcid_to_cid_token(ngtcp2_cid_token *dest, + const ngtcp2_dcid *src) { + dest->seq = src->seq; + dest->cid = src->cid; + ngtcp2_path_storage_init2(&dest->ps, &src->ps.path); + if ((dest->token_present = + (src->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT) != 0)) { + memcpy(dest->token, src->token, NGTCP2_STATELESS_RESET_TOKENLEN); + } +} + +size_t ngtcp2_conn_get_active_dcid(ngtcp2_conn *conn, ngtcp2_cid_token *dest) { + ngtcp2_pv *pv = conn->pv; + ngtcp2_cid_token *orig = dest; + ngtcp2_dcid *dcid; + size_t len, i; + + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED)) { + return 0; + } + + copy_dcid_to_cid_token(dest, &conn->dcid.current); + ++dest; + + if (pv) { + if (pv->dcid.seq != conn->dcid.current.seq) { + copy_dcid_to_cid_token(dest, &pv->dcid); + ++dest; + } + if ((pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE) && + pv->fallback_dcid.seq != conn->dcid.current.seq && + pv->fallback_dcid.seq != pv->dcid.seq) { + copy_dcid_to_cid_token(dest, &pv->fallback_dcid); + ++dest; + } + } + + len = ngtcp2_ringbuf_len(&conn->dcid.retired.rb); + for (i = 0; i < len; ++i) { + dcid = ngtcp2_ringbuf_get(&conn->dcid.retired.rb, i); + copy_dcid_to_cid_token(dest, dcid); + ++dest; + } + + return (size_t)(dest - orig); +} + +void ngtcp2_conn_set_local_addr(ngtcp2_conn *conn, const ngtcp2_addr *addr) { + ngtcp2_addr *dest = &conn->dcid.current.ps.path.local; + + assert(addr->addrlen <= + (ngtcp2_socklen)sizeof(conn->dcid.current.ps.local_addrbuf)); + ngtcp2_addr_copy(dest, addr); +} + +void ngtcp2_conn_set_path_user_data(ngtcp2_conn *conn, void *path_user_data) { + conn->dcid.current.ps.path.user_data = path_user_data; +} + +const ngtcp2_path *ngtcp2_conn_get_path(ngtcp2_conn *conn) { + return &conn->dcid.current.ps.path; +} + +size_t ngtcp2_conn_get_max_tx_udp_payload_size(ngtcp2_conn *conn) { + return conn->local.settings.max_tx_udp_payload_size; +} + +size_t ngtcp2_conn_get_path_max_tx_udp_payload_size(ngtcp2_conn *conn) { + if (conn->local.settings.no_tx_udp_payload_size_shaping) { + return ngtcp2_conn_get_max_tx_udp_payload_size(conn); + } + + return conn->dcid.current.max_udp_payload_size; +} + +static int conn_initiate_migration_precheck(ngtcp2_conn *conn, + const ngtcp2_addr *local_addr) { + if (!(conn->flags & NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED) || + conn->remote.transport_params->disable_active_migration || + conn->dcid.current.cid.datalen == 0 || + (conn->pv && (conn->pv->flags & NGTCP2_PV_FLAG_PREFERRED_ADDR))) { + return NGTCP2_ERR_INVALID_STATE; + } + + if (ngtcp2_ringbuf_len(&conn->dcid.unused.rb) == 0) { + return NGTCP2_ERR_CONN_ID_BLOCKED; + } + + if (ngtcp2_addr_eq(&conn->dcid.current.ps.path.local, local_addr)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + return 0; +} + +int ngtcp2_conn_initiate_immediate_migration(ngtcp2_conn *conn, + const ngtcp2_path *path, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_dcid *dcid; + ngtcp2_duration pto, initial_pto, timeout; + ngtcp2_pv *pv; + + assert(!conn->server); + + conn->log.last_ts = ts; + conn->qlog.last_ts = ts; + + rv = conn_initiate_migration_precheck(conn, &path->local); + if (rv != 0) { + return rv; + } + + ngtcp2_conn_stop_pmtud(conn); + + if (conn->pv) { + rv = conn_abort_pv(conn, ts); + if (rv != 0) { + return rv; + } + } + + rv = conn_retire_dcid(conn, &conn->dcid.current, ts); + if (rv != 0) { + return rv; + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ngtcp2_dcid_set_path(dcid, path); + + ngtcp2_dcid_copy(&conn->dcid.current, dcid); + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + + conn_reset_congestion_state(conn, ts); + conn_reset_ecn_validation_state(conn); + + pto = conn_compute_pto(conn, &conn->pktns); + initial_pto = conn_compute_initial_pto(conn, &conn->pktns); + timeout = 3 * ngtcp2_max(pto, initial_pto); + + /* TODO It might be better to add a new flag which indicates that a + connection should be closed if this path validation failed. The + current design allows an application to continue, by migrating + into yet another path. */ + rv = ngtcp2_pv_new(&pv, dcid, timeout, NGTCP2_PV_FLAG_NONE, &conn->log, + conn->mem); + if (rv != 0) { + return rv; + } + + conn->pv = pv; + + return conn_call_activate_dcid(conn, &conn->dcid.current); +} + +int ngtcp2_conn_initiate_migration(ngtcp2_conn *conn, const ngtcp2_path *path, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_dcid *dcid; + ngtcp2_duration pto, initial_pto, timeout; + ngtcp2_pv *pv; + + assert(!conn->server); + + conn->log.last_ts = ts; + conn->qlog.last_ts = ts; + + rv = conn_initiate_migration_precheck(conn, &path->local); + if (rv != 0) { + return rv; + } + + if (conn->pv) { + rv = conn_abort_pv(conn, ts); + if (rv != 0) { + return rv; + } + } + + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + ngtcp2_dcid_set_path(dcid, path); + + pto = conn_compute_pto(conn, &conn->pktns); + initial_pto = conn_compute_initial_pto(conn, &conn->pktns); + timeout = 3 * ngtcp2_max(pto, initial_pto); + + rv = ngtcp2_pv_new(&pv, dcid, timeout, NGTCP2_PV_FLAG_NONE, &conn->log, + conn->mem); + if (rv != 0) { + return rv; + } + + ngtcp2_ringbuf_pop_front(&conn->dcid.unused.rb); + conn->pv = pv; + + return conn_call_activate_dcid(conn, &pv->dcid); +} + +uint64_t ngtcp2_conn_get_max_local_streams_uni(ngtcp2_conn *conn) { + return conn->local.uni.max_streams; +} + +uint64_t ngtcp2_conn_get_max_data_left(ngtcp2_conn *conn) { + return conn->tx.max_offset - conn->tx.offset; +} + +uint64_t ngtcp2_conn_get_max_stream_data_left(ngtcp2_conn *conn, + int64_t stream_id) { + ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id); + + if (strm == NULL) { + return 0; + } + + return strm->tx.max_offset - strm->tx.offset; +} + +uint64_t ngtcp2_conn_get_streams_bidi_left(ngtcp2_conn *conn) { + uint64_t n = ngtcp2_ord_stream_id(conn->local.bidi.next_stream_id); + + return n > conn->local.bidi.max_streams + ? 0 + : conn->local.bidi.max_streams - n + 1; +} + +uint64_t ngtcp2_conn_get_streams_uni_left(ngtcp2_conn *conn) { + uint64_t n = ngtcp2_ord_stream_id(conn->local.uni.next_stream_id); + + return n > conn->local.uni.max_streams ? 0 + : conn->local.uni.max_streams - n + 1; +} + +uint64_t ngtcp2_conn_get_cwnd_left(ngtcp2_conn *conn) { + uint64_t bytes_in_flight = conn->cstat.bytes_in_flight; + uint64_t cwnd = conn_get_cwnd(conn); + + if (cwnd > bytes_in_flight) { + return cwnd - bytes_in_flight; + } + + return 0; +} + +ngtcp2_tstamp ngtcp2_conn_get_idle_expiry(ngtcp2_conn *conn) { + ngtcp2_duration trpto; + ngtcp2_duration idle_timeout; + + /* TODO Remote max_idle_timeout becomes effective after handshake + completion. */ + + if (!conn_is_handshake_completed(conn) || + conn->remote.transport_params->max_idle_timeout == 0 || + (conn->local.transport_params.max_idle_timeout && + conn->local.transport_params.max_idle_timeout < + conn->remote.transport_params->max_idle_timeout)) { + idle_timeout = conn->local.transport_params.max_idle_timeout; + } else { + idle_timeout = conn->remote.transport_params->max_idle_timeout; + } + + if (idle_timeout == 0) { + return UINT64_MAX; + } + + trpto = 3 * conn_compute_pto(conn, conn_is_handshake_completed(conn) + ? &conn->pktns + : conn->hs_pktns); + + return conn->idle_ts + ngtcp2_max(idle_timeout, trpto); +} + +ngtcp2_duration ngtcp2_conn_get_pto(ngtcp2_conn *conn) { + return conn_compute_pto( + conn, conn_is_handshake_completed(conn) ? &conn->pktns : conn->hs_pktns); +} + +void ngtcp2_conn_set_initial_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx) { + assert(conn->in_pktns); + conn->in_pktns->crypto.ctx = *ctx; +} + +const ngtcp2_crypto_ctx *ngtcp2_conn_get_initial_crypto_ctx(ngtcp2_conn *conn) { + assert(conn->in_pktns); + return &conn->in_pktns->crypto.ctx; +} + +void ngtcp2_conn_set_retry_aead(ngtcp2_conn *conn, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx) { + assert(!conn->crypto.retry_aead_ctx.native_handle); + + conn->crypto.retry_aead = *aead; + conn->crypto.retry_aead_ctx = *aead_ctx; +} + +void ngtcp2_conn_set_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx) { + assert(conn->hs_pktns); + conn->hs_pktns->crypto.ctx = *ctx; + conn->pktns.crypto.ctx = *ctx; +} + +const ngtcp2_crypto_ctx *ngtcp2_conn_get_crypto_ctx(ngtcp2_conn *conn) { + return &conn->pktns.crypto.ctx; +} + +void ngtcp2_conn_set_early_crypto_ctx(ngtcp2_conn *conn, + const ngtcp2_crypto_ctx *ctx) { + conn->early.ctx = *ctx; +} + +const ngtcp2_crypto_ctx *ngtcp2_conn_get_early_crypto_ctx(ngtcp2_conn *conn) { + return &conn->early.ctx; +} + +void *ngtcp2_conn_get_tls_native_handle(ngtcp2_conn *conn) { + return conn->crypto.tls_native_handle; +} + +void ngtcp2_conn_set_tls_native_handle(ngtcp2_conn *conn, + void *tls_native_handle) { + conn->crypto.tls_native_handle = tls_native_handle; +} + +void ngtcp2_conn_get_connection_close_error( + ngtcp2_conn *conn, ngtcp2_connection_close_error *ccerr) { + *ccerr = conn->rx.ccerr; +} + +void ngtcp2_conn_set_tls_error(ngtcp2_conn *conn, int liberr) { + conn->crypto.tls_error = liberr; +} + +int ngtcp2_conn_get_tls_error(ngtcp2_conn *conn) { + return conn->crypto.tls_error; +} + +void ngtcp2_conn_set_tls_alert(ngtcp2_conn *conn, uint8_t alert) { + conn->crypto.tls_alert = alert; +} + +uint8_t ngtcp2_conn_get_tls_alert(ngtcp2_conn *conn) { + return conn->crypto.tls_alert; +} + +int ngtcp2_conn_is_local_stream(ngtcp2_conn *conn, int64_t stream_id) { + return conn_local_stream(conn, stream_id); +} + +int ngtcp2_conn_is_server(ngtcp2_conn *conn) { return conn->server; } + +int ngtcp2_conn_after_retry(ngtcp2_conn *conn) { + return (conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) != 0; +} + +int ngtcp2_conn_set_stream_user_data(ngtcp2_conn *conn, int64_t stream_id, + void *stream_user_data) { + ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id); + + if (strm == NULL) { + return NGTCP2_ERR_STREAM_NOT_FOUND; + } + + strm->stream_user_data = stream_user_data; + + return 0; +} + +void ngtcp2_conn_update_pkt_tx_time(ngtcp2_conn *conn, ngtcp2_tstamp ts) { + double pacing_rate; + ngtcp2_duration interval; + + if (conn->tx.pacing.pktlen == 0) { + return; + } + + if (conn->cstat.pacing_rate > 0) { + pacing_rate = conn->cstat.pacing_rate; + } else { + /* 1.25 is the under-utilization avoidance factor described in + https://datatracker.ietf.org/doc/html/rfc9002#section-7.7 */ + pacing_rate = + (double)conn->cstat.cwnd / (double)conn->cstat.smoothed_rtt * 1.25; + } + + interval = (ngtcp2_duration)((double)conn->tx.pacing.pktlen / pacing_rate); + + conn->tx.pacing.next_ts = ts + interval; + conn->tx.pacing.pktlen = 0; +} + +size_t ngtcp2_conn_get_send_quantum(ngtcp2_conn *conn) { + return conn->cstat.send_quantum; +} + +int ngtcp2_conn_track_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { + size_t i; + + if (conn->dcid.retire_unacked.len >= + ngtcp2_arraylen(conn->dcid.retire_unacked.seqs)) { + return NGTCP2_ERR_CONNECTION_ID_LIMIT; + } + + /* Make sure that we do not have a duplicate */ + for (i = 0; i < conn->dcid.retire_unacked.len; ++i) { + if (conn->dcid.retire_unacked.seqs[i] == seq) { + ngtcp2_unreachable(); + } + } + + conn->dcid.retire_unacked.seqs[conn->dcid.retire_unacked.len++] = seq; + + return 0; +} + +void ngtcp2_conn_untrack_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq) { + size_t i; + + for (i = 0; i < conn->dcid.retire_unacked.len; ++i) { + if (conn->dcid.retire_unacked.seqs[i] != seq) { + continue; + } + + if (i != conn->dcid.retire_unacked.len - 1) { + conn->dcid.retire_unacked.seqs[i] = + conn->dcid.retire_unacked.seqs[conn->dcid.retire_unacked.len - 1]; + } + + --conn->dcid.retire_unacked.len; + + return; + } +} + +size_t ngtcp2_conn_get_stream_loss_count(ngtcp2_conn *conn, int64_t stream_id) { + ngtcp2_strm *strm = ngtcp2_conn_find_stream(conn, stream_id); + + if (strm == NULL) { + return 0; + } + + return strm->tx.loss_count; +} + +void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent, + const ngtcp2_path *path, + const uint8_t *data) { + ngtcp2_path_storage_init2(&pcent->ps, path); + memcpy(pcent->data, data, sizeof(pcent->data)); +} + +void ngtcp2_settings_default_versioned(int settings_version, + ngtcp2_settings *settings) { + (void)settings_version; + + memset(settings, 0, sizeof(*settings)); + settings->cc_algo = NGTCP2_CC_ALGO_CUBIC; + settings->initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; + settings->ack_thresh = 2; + settings->max_tx_udp_payload_size = 1500 - 48; + settings->handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT; +} + +void ngtcp2_transport_params_default_versioned( + int transport_params_version, ngtcp2_transport_params *params) { + (void)transport_params_version; + + memset(params, 0, sizeof(*params)); + params->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + params->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + params->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; + params->active_connection_id_limit = + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; +} + +/* The functions prefixed with ngtcp2_pkt_ are usually put inside + ngtcp2_pkt.c. This function uses encryption construct and uses + test data defined only in ngtcp2_conn_test.c, so it is written + here. */ +ngtcp2_ssize ngtcp2_pkt_write_connection_close( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, uint64_t error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, const uint8_t *iv, + ngtcp2_hp_mask hp_mask, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx) { + ngtcp2_pkt_hd hd; + ngtcp2_crypto_km ckm; + ngtcp2_crypto_cc cc; + ngtcp2_ppe ppe; + ngtcp2_frame fr = {0}; + int rv; + + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_INITIAL, dcid, + scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version, + /* len = */ 0); + + ngtcp2_vec_init(&ckm.secret, NULL, 0); + ngtcp2_vec_init(&ckm.iv, iv, 12); + ckm.aead_ctx = *aead_ctx; + ckm.pkt_num = 0; + ckm.flags = NGTCP2_CRYPTO_KM_FLAG_NONE; + + cc.aead = *aead; + cc.hp = *hp; + cc.ckm = &ckm; + cc.hp_ctx = *hp_ctx; + cc.encrypt = encrypt; + cc.hp_mask = hp_mask; + + ngtcp2_ppe_init(&ppe, dest, destlen, &cc); + + rv = ngtcp2_ppe_encode_hd(&ppe, &hd); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return rv; + } + + if (!ngtcp2_ppe_ensure_hp_sample(&ppe)) { + return NGTCP2_ERR_NOBUF; + } + + fr.type = NGTCP2_FRAME_CONNECTION_CLOSE; + fr.connection_close.error_code = error_code; + fr.connection_close.reasonlen = reasonlen; + fr.connection_close.reason = (uint8_t *)reason; + + rv = ngtcp2_ppe_encode_frame(&ppe, &fr); + if (rv != 0) { + assert(NGTCP2_ERR_NOBUF == rv); + return rv; + } + + return ngtcp2_ppe_final(&ppe, NULL); +} + +int ngtcp2_is_bidi_stream(int64_t stream_id) { return bidi_stream(stream_id); } + +uint32_t ngtcp2_select_version(const uint32_t *preferred_versions, + size_t preferred_versionslen, + const uint32_t *offered_versions, + size_t offered_versionslen) { + size_t i, j; + + if (!preferred_versionslen || !offered_versionslen) { + return 0; + } + + for (i = 0; i < preferred_versionslen; ++i) { + assert(ngtcp2_is_supported_version(preferred_versions[i])); + + for (j = 0; j < offered_versionslen; ++j) { + if (preferred_versions[i] == offered_versions[j]) { + return preferred_versions[i]; + } + } + } + + return 0; +} diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h new file mode 100644 index 0000000..aef2380 --- /dev/null +++ b/lib/ngtcp2_conn.h @@ -0,0 +1,1115 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CONN_H +#define NGTCP2_CONN_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_crypto.h" +#include "ngtcp2_acktr.h" +#include "ngtcp2_rtb.h" +#include "ngtcp2_strm.h" +#include "ngtcp2_idtr.h" +#include "ngtcp2_str.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_log.h" +#include "ngtcp2_pq.h" +#include "ngtcp2_cc.h" +#include "ngtcp2_bbr.h" +#include "ngtcp2_bbr2.h" +#include "ngtcp2_pv.h" +#include "ngtcp2_pmtud.h" +#include "ngtcp2_cid.h" +#include "ngtcp2_buf.h" +#include "ngtcp2_ppe.h" +#include "ngtcp2_qlog.h" +#include "ngtcp2_rst.h" + +typedef enum { + /* Client specific handshake states */ + NGTCP2_CS_CLIENT_INITIAL, + NGTCP2_CS_CLIENT_WAIT_HANDSHAKE, + NGTCP2_CS_CLIENT_TLS_HANDSHAKE_FAILED, + /* Server specific handshake states */ + NGTCP2_CS_SERVER_INITIAL, + NGTCP2_CS_SERVER_WAIT_HANDSHAKE, + NGTCP2_CS_SERVER_TLS_HANDSHAKE_FAILED, + /* Shared by both client and server */ + NGTCP2_CS_POST_HANDSHAKE, + NGTCP2_CS_CLOSING, + NGTCP2_CS_DRAINING, +} ngtcp2_conn_state; + +/* NGTCP2_MAX_STREAMS is the maximum number of streams. */ +#define NGTCP2_MAX_STREAMS (1LL << 60) + +/* NGTCP2_MAX_NUM_BUFFED_RX_PKTS is the maximum number of buffered + reordered packets. */ +#define NGTCP2_MAX_NUM_BUFFED_RX_PKTS 4 + +/* NGTCP2_MAX_REORDERED_CRYPTO_DATA is the maximum offset of crypto + data which is not continuous. In other words, there is a gap of + unreceived data. */ +#define NGTCP2_MAX_REORDERED_CRYPTO_DATA 65536 + +/* NGTCP2_MAX_RX_INITIAL_CRYPTO_DATA is the maximum offset of received + crypto stream in Initial packet. We set this hard limit here + because crypto stream is unbounded. */ +#define NGTCP2_MAX_RX_INITIAL_CRYPTO_DATA 65536 +/* NGTCP2_MAX_RX_HANDSHAKE_CRYPTO_DATA is the maximum offset of + received crypto stream in Handshake packet. We set this hard limit + here because crypto stream is unbounded. */ +#define NGTCP2_MAX_RX_HANDSHAKE_CRYPTO_DATA 65536 + +/* NGTCP2_MAX_RETRIES is the number of Retry packet which client can + accept. */ +#define NGTCP2_MAX_RETRIES 3 + +/* NGTCP2_MAX_BOUND_DCID_POOL_SIZE is the maximum number of + destination connection ID which have been bound to a particular + path, but not yet used as primary path and path validation is not + performed from the local endpoint. */ +#define NGTCP2_MAX_BOUND_DCID_POOL_SIZE 4 +/* NGTCP2_MAX_DCID_POOL_SIZE is the maximum number of destination + connection ID the remote endpoint provides to store. It must be + the power of 2. */ +#define NGTCP2_MAX_DCID_POOL_SIZE 8 +/* NGTCP2_MAX_DCID_RETIRED_SIZE is the maximum number of retired DCID + kept to catch in-flight packet on retired path. */ +#define NGTCP2_MAX_DCID_RETIRED_SIZE 2 +/* NGTCP2_MAX_SCID_POOL_SIZE is the maximum number of source + connection ID the local endpoint provides to the remote endpoint. + The chosen value was described in old draft. Now a remote endpoint + tells the maximum value. The value can be quite large, and we have + to put the sane limit.*/ +#define NGTCP2_MAX_SCID_POOL_SIZE 8 + +/* NGTCP2_MAX_NON_ACK_TX_PKT is the maximum number of continuous non + ACK-eliciting packets. */ +#define NGTCP2_MAX_NON_ACK_TX_PKT 3 + +/* NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS is the maximum number of ECN marked + packets sent in NGTCP2_ECN_STATE_TESTING period. */ +#define NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS 10 + +/* NGTCP2_CONNECTION_CLOSE_ERROR_MAX_REASONLEN is the maximum length + of reason phrase to remember. If the received reason phrase is + longer than this value, it is truncated. */ +#define NGTCP2_CONNECTION_CLOSE_ERROR_MAX_REASONLEN 1024 + +/* NGTCP2_WRITE_PKT_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_WRITE_PKT_FLAG_NONE 0x00u +/* NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING indicates that packet other + than Initial packet should be padded. Initial packet might be + padded based on QUIC requirement regardless of this flag. */ +#define NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING 0x01u +/* NGTCP2_WRITE_PKT_FLAG_MORE indicates that more frames might come + and it should be encoded into the current packet. */ +#define NGTCP2_WRITE_PKT_FLAG_MORE 0x02u + +/* + * ngtcp2_max_frame is defined so that it covers the largest ACK + * frame. + */ +typedef union ngtcp2_max_frame { + ngtcp2_frame fr; + struct { + ngtcp2_ack ack; + /* ack includes 1 ngtcp2_ack_range. */ + ngtcp2_ack_range ranges[NGTCP2_MAX_ACK_RANGES - 1]; + } ackfr; +} ngtcp2_max_frame; + +typedef struct ngtcp2_path_challenge_entry { + ngtcp2_path_storage ps; + uint8_t data[8]; +} ngtcp2_path_challenge_entry; + +void ngtcp2_path_challenge_entry_init(ngtcp2_path_challenge_entry *pcent, + const ngtcp2_path *path, + const uint8_t *data); + +/* NGTCP2_CONN_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_CONN_FLAG_NONE 0x00u +/* NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED is set when TLS stack declares + that TLS handshake has completed. The condition of this + declaration varies between TLS implementations and this flag does + not indicate the completion of QUIC handshake. Some + implementations declare TLS handshake completion as server when + they write off Server Finished and before deriving application rx + secret. */ +#define NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED 0x01u +/* NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED is set if connection ID is + negotiated. This is only used for client. */ +#define NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED 0x02u +/* NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED is set if transport + parameters are received. */ +#define NGTCP2_CONN_FLAG_TRANSPORT_PARAM_RECVED 0x04u +/* NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED is set when a + local transport parameters are applied. */ +#define NGTCP2_CONN_FLAG_LOCAL_TRANSPORT_PARAMS_COMMITTED 0x08u +/* NGTCP2_CONN_FLAG_RECV_RETRY is set when a client receives Retry + packet. */ +#define NGTCP2_CONN_FLAG_RECV_RETRY 0x10u +/* NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED is set when 0-RTT packet is + rejected by a peer. */ +#define NGTCP2_CONN_FLAG_EARLY_DATA_REJECTED 0x20u +/* NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED is set when the expired + keep-alive timer has been cancelled. */ +#define NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED 0x40u +/* NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED is set when an endpoint + confirmed completion of handshake. */ +#define NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED 0x80u +/* NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED is set when the + library transitions its state to "post handshake". */ +#define NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED 0x0100u +/* NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT is set when the early + handshake retransmission has done when server receives overlapping + Initial crypto data. */ +#define NGTCP2_CONN_FLAG_HANDSHAKE_EARLY_RETRANSMIT 0x0200u +/* NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT indicates that the local endpoint + sends a QUIC packet without Fixed Bit set if a remote endpoint + supports Greasing QUIC Bit extension. */ +#define NGTCP2_CONN_FLAG_CLEAR_FIXED_BIT 0x0400u +/* NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED is set when key update is + not confirmed by the local endpoint. That is, it has not received + ACK frame which acknowledges packet which is encrypted with new + key. */ +#define NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED 0x0800u +/* NGTCP2_CONN_FLAG_PPE_PENDING is set when + NGTCP2_WRITE_STREAM_FLAG_MORE is used and the intermediate state of + ngtcp2_ppe is stored in pkt struct of ngtcp2_conn. */ +#define NGTCP2_CONN_FLAG_PPE_PENDING 0x1000u +/* NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE is set when idle timer + should be restarted on next write. */ +#define NGTCP2_CONN_FLAG_RESTART_IDLE_TIMER_ON_WRITE 0x2000u +/* NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED indicates that server as peer + verified client address. This flag is only used by client. */ +#define NGTCP2_CONN_FLAG_SERVER_ADDR_VERIFIED 0x4000u +/* NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED indicates that an early key is + installed. conn->early.ckm cannot be used for this purpose because + it might be discarded when a certain condition is met. */ +#define NGTCP2_CONN_FLAG_EARLY_KEY_INSTALLED 0x8000u +/* NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR is set when the local + endpoint has initiated key update. */ +#define NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR 0x10000u + +typedef struct ngtcp2_crypto_data { + ngtcp2_buf buf; + /* pkt_type is the type of packet to send data in buf. If it is 0, + it must be sent in Short packet. Otherwise, it is sent the long + packet type denoted by pkt_type. */ + uint8_t pkt_type; +} ngtcp2_crypto_data; + +typedef struct ngtcp2_pktns { + struct { + /* last_pkt_num is the packet number which the local endpoint sent + last time.*/ + int64_t last_pkt_num; + ngtcp2_frame_chain *frq; + /* num_non_ack_pkt is the number of continuous non ACK-eliciting + packets. */ + size_t num_non_ack_pkt; + + struct { + /* ect0 is the number of QUIC packets, not UDP datagram, which + are sent in UDP datagram with ECT0 marking. */ + size_t ect0; + /* start_pkt_num is the lowest packet number that are sent + during ECN validation period. */ + int64_t start_pkt_num; + /* validation_pkt_sent is the number of QUIC packets sent during + validation period. */ + size_t validation_pkt_sent; + /* validation_pkt_lost is the number of QUIC packets lost during + validation period. */ + size_t validation_pkt_lost; + } ecn; + } tx; + + struct { + /* pngap tracks received packet number in order to suppress + duplicated packet number. */ + ngtcp2_gaptr pngap; + /* max_pkt_num is the largest packet number received so far. */ + int64_t max_pkt_num; + /* max_pkt_ts is the timestamp when max_pkt_num packet is + received. */ + ngtcp2_tstamp max_pkt_ts; + /* max_ack_eliciting_pkt_num is the largest ack-eliciting packet + number received so far. */ + int64_t max_ack_eliciting_pkt_num; + /* + * buffed_pkts is buffered packets which cannot be decrypted with + * the current encryption level. + * + * In server Initial encryption level, 0-RTT packet may be buffered. + * In server Handshake encryption level, Short packet may be buffered. + * + * In client Initial encryption level, Handshake or Short packet may + * be buffered. In client Handshake encryption level, Short packet + * may be buffered. + * + * - 0-RTT packet is only buffered in server Initial encryption + * level ngtcp2_pktns. + * + * - Handshake packet is only buffered in client Handshake + * encryption level ngtcp2_pktns. + * + * - Short packet is only buffered in Short encryption level + * ngtcp2_pktns. + */ + ngtcp2_pkt_chain *buffed_pkts; + + struct { + /* ect0, ect1, and ce are the number of QUIC packets received + with those markings. */ + size_t ect0; + size_t ect1; + size_t ce; + struct { + /* ect0, ect1, ce are the ECN counts received in the latest + ACK frame. */ + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + } ack; + } ecn; + } rx; + + struct { + struct { + /* frq contains crypto data sorted by their offset. */ + ngtcp2_ksl frq; + /* offset is the offset of crypto stream in this packet number + space. */ + uint64_t offset; + /* ckm is a cryptographic key, and iv to encrypt outgoing + packets. */ + ngtcp2_crypto_km *ckm; + /* hp_ctx is cipher context for packet header protection. */ + ngtcp2_crypto_cipher_ctx hp_ctx; + /* data is the submitted crypto data. */ + ngtcp2_buf_chain *data; + } tx; + + struct { + /* ckm is a cryptographic key, and iv to decrypt incoming + packets. */ + ngtcp2_crypto_km *ckm; + /* hp_ctx is cipher context for packet header protection. */ + ngtcp2_crypto_cipher_ctx hp_ctx; + } rx; + + ngtcp2_strm strm; + ngtcp2_crypto_ctx ctx; + } crypto; + + ngtcp2_acktr acktr; + ngtcp2_rtb rtb; +} ngtcp2_pktns; + +typedef enum ngtcp2_ecn_state { + NGTCP2_ECN_STATE_TESTING, + NGTCP2_ECN_STATE_UNKNOWN, + NGTCP2_ECN_STATE_FAILED, + NGTCP2_ECN_STATE_CAPABLE, +} ngtcp2_ecn_state; + +ngtcp2_static_ringbuf_def(dcid_bound, NGTCP2_MAX_BOUND_DCID_POOL_SIZE, + sizeof(ngtcp2_dcid)); +ngtcp2_static_ringbuf_def(dcid_unused, NGTCP2_MAX_DCID_POOL_SIZE, + sizeof(ngtcp2_dcid)); +ngtcp2_static_ringbuf_def(dcid_retired, NGTCP2_MAX_DCID_RETIRED_SIZE, + sizeof(ngtcp2_dcid)); +ngtcp2_static_ringbuf_def(path_challenge, 4, + sizeof(ngtcp2_path_challenge_entry)); + +ngtcp2_objalloc_def(strm, ngtcp2_strm, oplent); + +struct ngtcp2_conn { + ngtcp2_objalloc frc_objalloc; + ngtcp2_objalloc rtb_entry_objalloc; + ngtcp2_objalloc strm_objalloc; + ngtcp2_conn_state state; + ngtcp2_callbacks callbacks; + /* rcid is a connection ID present in Initial or 0-RTT packet from + client as destination connection ID. Server uses this field to + check that duplicated Initial or 0-RTT packet are indeed sent to + this connection. Client uses this field to validate + original_destination_connection_id transport parameter. */ + ngtcp2_cid rcid; + /* oscid is the source connection ID initially used by the local + endpoint. */ + ngtcp2_cid oscid; + /* retry_scid is the source connection ID from Retry packet. Client + records it in order to verify retry_source_connection_id + transport parameter. Server does not use this field. */ + ngtcp2_cid retry_scid; + ngtcp2_pktns *in_pktns; + ngtcp2_pktns *hs_pktns; + ngtcp2_pktns pktns; + + struct { + /* current is the current destination connection ID. */ + ngtcp2_dcid current; + /* bound is a set of destination connection IDs which are bound to + particular paths. These paths are not validated yet. */ + ngtcp2_static_ringbuf_dcid_bound bound; + /* unused is a set of unused CID received from peer. */ + ngtcp2_static_ringbuf_dcid_unused unused; + /* retired is a set of CID retired by local endpoint. Keep them + in 3*PTO to catch packets in flight along the old path. */ + ngtcp2_static_ringbuf_dcid_retired retired; + /* seqgap tracks received sequence numbers in order to ignore + retransmitted duplicated NEW_CONNECTION_ID frame. */ + ngtcp2_gaptr seqgap; + /* retire_prior_to is the largest retire_prior_to received so + far. */ + uint64_t retire_prior_to; + struct { + /* seqs contains sequence number of Connection ID whose + retirement is not acknowledged by the remote endpoint yet. */ + uint64_t seqs[NGTCP2_MAX_DCID_POOL_SIZE * 2]; + /* len is the number of sequence numbers that seq contains. */ + size_t len; + } retire_unacked; + /* zerolen_seq is a pseudo sequence number of zero-length + Destination Connection ID in order to distinguish between + them. */ + uint64_t zerolen_seq; + } dcid; + + struct { + /* set is a set of CID sent to peer. The peer can use any CIDs in + this set. This includes used CID as well as unused ones. */ + ngtcp2_ksl set; + /* used is a set of CID used by peer. The sort function of this + priority queue takes timestamp when CID is retired and sorts + them in ascending order. */ + ngtcp2_pq used; + /* last_seq is the last sequence number of connection ID. */ + uint64_t last_seq; + /* num_retired is the number of retired Connection ID still + included in set. */ + size_t num_retired; + } scid; + + struct { + /* strmq contains ngtcp2_strm which has frames to send. */ + ngtcp2_pq strmq; + /* strmq_nretrans is the number of entries in strmq which has + stream data to resent. */ + size_t strmq_nretrans; + /* ack is ACK frame. The underlying buffer is reused. */ + ngtcp2_frame *ack; + /* max_ack_ranges is the number of additional ngtcp2_ack_range + which ack can contain. */ + size_t max_ack_ranges; + /* offset is the offset the local endpoint has sent to the remote + endpoint. */ + uint64_t offset; + /* max_offset is the maximum offset that local endpoint can + send. */ + uint64_t max_offset; + /* last_max_data_ts is the timestamp when last MAX_DATA frame is + sent. */ + ngtcp2_tstamp last_max_data_ts; + + struct { + /* state is the state of ECN validation */ + ngtcp2_ecn_state state; + /* validation_start_ts is the timestamp when ECN validation is + started. It is UINT64_MAX if it has not started yet. */ + ngtcp2_tstamp validation_start_ts; + /* dgram_sent is the number of UDP datagram sent during ECN + validation period. */ + size_t dgram_sent; + } ecn; + + struct { + /* pktlen is the number of bytes written before calling + ngtcp2_conn_update_pkt_tx_time which resets this field to + 0. */ + size_t pktlen; + /* next_ts is the time to send next packet. It is UINT64_MAX if + packet pacing is disabled or expired.*/ + ngtcp2_tstamp next_ts; + } pacing; + } tx; + + struct { + /* unsent_max_offset is the maximum offset that remote endpoint + can send without extending MAX_DATA. This limit is not yet + notified to the remote endpoint. */ + uint64_t unsent_max_offset; + /* offset is the cumulative sum of stream data received for this + connection. */ + uint64_t offset; + /* max_offset is the maximum offset that remote endpoint can + send. */ + uint64_t max_offset; + /* window is the connection-level flow control window size. */ + uint64_t window; + /* path_challenge stores received PATH_CHALLENGE data. */ + ngtcp2_static_ringbuf_path_challenge path_challenge; + /* ccerr is the received connection close error. */ + ngtcp2_connection_close_error ccerr; + } rx; + + struct { + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx hp_ctx; + ngtcp2_crypto_ctx ctx; + /* discard_started_ts is the timestamp when the timer to discard + early key has started. Used by server only. */ + ngtcp2_tstamp discard_started_ts; + /* transport_params is the values remembered by client from the + previous session. These are set by + ngtcp2_conn_set_early_remote_transport_params(). Server does + not use this field. Server must not set values for these + parameters that are smaller than the remembered values. */ + struct { + uint64_t initial_max_streams_bidi; + uint64_t initial_max_streams_uni; + uint64_t initial_max_stream_data_bidi_local; + uint64_t initial_max_stream_data_bidi_remote; + uint64_t initial_max_stream_data_uni; + uint64_t initial_max_data; + uint64_t active_connection_id_limit; + uint64_t max_datagram_frame_size; + } transport_params; + } early; + + struct { + ngtcp2_settings settings; + /* transport_params is the local transport parameters. It is used + for Short packet only. */ + ngtcp2_transport_params transport_params; + struct { + /* max_streams is the maximum number of bidirectional streams which + the local endpoint can open. */ + uint64_t max_streams; + /* next_stream_id is the bidirectional stream ID which the local + endpoint opens next. */ + int64_t next_stream_id; + } bidi; + + struct { + /* max_streams is the maximum number of unidirectional streams + which the local endpoint can open. */ + uint64_t max_streams; + /* next_stream_id is the unidirectional stream ID which the + local endpoint opens next. */ + int64_t next_stream_id; + } uni; + } local; + + struct { + /* transport_params is the received transport parameters during + handshake. It is used for Short packet only. */ + ngtcp2_transport_params *transport_params; + /* pending_transport_params is received transport parameters + during handshake. It is copied to transport_params when 1RTT + key is available. */ + ngtcp2_transport_params *pending_transport_params; + struct { + ngtcp2_idtr idtr; + /* unsent_max_streams is the maximum number of streams of peer + initiated bidirectional stream which the local endpoint can + accept. This limit is not yet notified to the remote + endpoint. */ + uint64_t unsent_max_streams; + /* max_streams is the maximum number of streams of peer + initiated bidirectional stream which the local endpoint can + accept. */ + uint64_t max_streams; + } bidi; + + struct { + ngtcp2_idtr idtr; + /* unsent_max_streams is the maximum number of streams of peer + initiated unidirectional stream which the local endpoint can + accept. This limit is not yet notified to the remote + endpoint. */ + uint64_t unsent_max_streams; + /* max_streams is the maximum number of streams of peer + initiated unidirectional stream which the local endpoint can + accept. */ + uint64_t max_streams; + } uni; + } remote; + + struct { + struct { + /* new_tx_ckm is a new sender 1RTT key which has not been + used. */ + ngtcp2_crypto_km *new_tx_ckm; + /* new_rx_ckm is a new receiver 1RTT key which has not + successfully decrypted incoming packet yet. */ + ngtcp2_crypto_km *new_rx_ckm; + /* old_rx_ckm is an old receiver 1RTT key. */ + ngtcp2_crypto_km *old_rx_ckm; + /* confirmed_ts is the time instant when the key update is + confirmed by the local endpoint last time. UINT64_MAX means + undefined value. */ + ngtcp2_tstamp confirmed_ts; + } key_update; + + /* tls_native_handle is a native handle to TLS session object. */ + void *tls_native_handle; + /* decrypt_hp_buf is a buffer which is used to write unprotected + packet header. */ + ngtcp2_vec decrypt_hp_buf; + /* decrypt_buf is a buffer which is used to write decrypted data. */ + ngtcp2_vec decrypt_buf; + /* retry_aead is AEAD to verify Retry packet integrity. It is + used by client only. */ + ngtcp2_crypto_aead retry_aead; + /* retry_aead_ctx is AEAD cipher context to verify Retry packet + integrity. It is used by client only. */ + ngtcp2_crypto_aead_ctx retry_aead_ctx; + /* tls_error is TLS related error. */ + int tls_error; + /* tls_alert is TLS alert generated by the local endpoint. */ + uint8_t tls_alert; + /* decryption_failure_count is the number of received packets that + fail authentication. */ + uint64_t decryption_failure_count; + } crypto; + + /* pkt contains the packet intermediate construction data to support + NGTCP2_WRITE_STREAM_FLAG_MORE */ + struct { + ngtcp2_crypto_cc cc; + ngtcp2_pkt_hd hd; + ngtcp2_ppe ppe; + ngtcp2_frame_chain **pfrc; + int pkt_empty; + int hd_logged; + /* flags is bitwise OR of zero or more of + NGTCP2_RTB_ENTRY_FLAG_*. */ + uint16_t rtb_entry_flags; + ngtcp2_ssize hs_spktlen; + int require_padding; + } pkt; + + struct { + /* last_ts is a timestamp when a last packet is sent or received + on a current path. */ + ngtcp2_tstamp last_ts; + /* timeout is keep-alive timeout. When it expires, a packet + should be sent to a current path to keep connection alive. It + might be used to keep NAT binding intact. If 0 is set, + keep-alive timer is disabled. */ + ngtcp2_duration timeout; + } keep_alive; + + struct { + /* Initial keys for negotiated version. If original version == + negotiated version, these fields are not used. */ + struct { + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx hp_ctx; + } rx; + struct { + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx hp_ctx; + } tx; + /* version is QUIC version that the above Initial keys are created + for. */ + uint32_t version; + /* preferred_versions is the array of versions that are preferred + by the local endpoint. Server negotiates one of those versions + in this array if a client initially selects a less preferred + version. Client uses this field and original_version field to + prevent version downgrade attack if it reacted upon Version + Negotiation packet. */ + uint32_t *preferred_versions; + /* preferred_versionslen is the number of versions stored in the + array pointed by preferred_versions. This field is only used + by server. */ + size_t preferred_versionslen; + /* other_versions is the versions that the local endpoint sends in + version_information transport parameter. This is the wire + image of other_versions field of version_information transport + parameter. */ + uint8_t *other_versions; + /* other_versionslen is the length of data pointed by + other_versions field. */ + size_t other_versionslen; + } vneg; + + ngtcp2_map strms; + ngtcp2_conn_stat cstat; + ngtcp2_pv *pv; + ngtcp2_pmtud *pmtud; + ngtcp2_log log; + ngtcp2_qlog qlog; + ngtcp2_rst rst; + ngtcp2_cc_algo cc_algo; + ngtcp2_cc cc; + const ngtcp2_mem *mem; + /* idle_ts is the time instant when idle timer started. */ + ngtcp2_tstamp idle_ts; + void *user_data; + uint32_t client_chosen_version; + uint32_t negotiated_version; + /* flags is bitwise OR of zero or more of NGTCP2_CONN_FLAG_*. */ + uint32_t flags; + int server; +}; + +typedef enum ngtcp2_vmsg_type { + NGTCP2_VMSG_TYPE_STREAM, + NGTCP2_VMSG_TYPE_DATAGRAM, +} ngtcp2_vmsg_type; + +typedef struct ngtcp2_vmsg_stream { + /* strm is a stream that data is sent to. */ + ngtcp2_strm *strm; + /* flags is bitwise OR of zero or more of + NGTCP2_WRITE_STREAM_FLAG_*. */ + uint32_t flags; + /* data is the pointer to ngtcp2_vec array which contains the stream + data to send. */ + const ngtcp2_vec *data; + /* datacnt is the number of ngtcp2_vec pointed by data. */ + size_t datacnt; + /* pdatalen is the pointer to the variable which the number of bytes + written is assigned to if pdatalen is not NULL. */ + ngtcp2_ssize *pdatalen; +} ngtcp2_vmsg_stream; + +typedef struct ngtcp2_vmsg_datagram { + /* data is the pointer to ngtcp2_vec array which contains the data + to send. */ + const ngtcp2_vec *data; + /* datacnt is the number of ngtcp2_vec pointed by data. */ + size_t datacnt; + /* dgram_id is an opaque identifier chosen by an application. */ + uint64_t dgram_id; + /* flags is bitwise OR of zero or more of + NGTCP2_WRITE_DATAGRAM_FLAG_*. */ + uint32_t flags; + /* paccepted is the pointer to the variable which, if it is not + NULL, is assigned nonzero if data is written to a packet. */ + int *paccepted; +} ngtcp2_vmsg_datagram; + +typedef struct ngtcp2_vmsg { + ngtcp2_vmsg_type type; + union { + ngtcp2_vmsg_stream stream; + ngtcp2_vmsg_datagram datagram; + }; +} ngtcp2_vmsg; + +/* + * ngtcp2_conn_sched_ack stores packet number |pkt_num| and its + * reception timestamp |ts| in order to send its ACK. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_PROTO + * Same packet number has already been added. + */ +int ngtcp2_conn_sched_ack(ngtcp2_conn *conn, ngtcp2_acktr *acktr, + int64_t pkt_num, int active_ack, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_find_stream returns a stream whose stream ID is + * |stream_id|. If no such stream is found, it returns NULL. + */ +ngtcp2_strm *ngtcp2_conn_find_stream(ngtcp2_conn *conn, int64_t stream_id); + +/* + * conn_init_stream initializes |strm|. Its stream ID is |stream_id|. + * This function adds |strm| to conn->strms. |strm| must be allocated + * by the caller. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + * NGTCP2_ERR_CALLBACK_FAILURE + * User-callback function failed. + */ +int ngtcp2_conn_init_stream(ngtcp2_conn *conn, ngtcp2_strm *strm, + int64_t stream_id, void *stream_user_data); + +/* + * ngtcp2_conn_close_stream closes stream |strm|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Stream is not found. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +int ngtcp2_conn_close_stream(ngtcp2_conn *conn, ngtcp2_strm *strm); + +/* + * ngtcp2_conn_close_stream closes stream |strm| if no further + * transmission and reception are allowed, and all reordered incoming + * data are emitted to the application, and the transmitted data are + * acked. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Stream is not found. + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +int ngtcp2_conn_close_stream_if_shut_rdwr(ngtcp2_conn *conn, ngtcp2_strm *strm); + +/* + * ngtcp2_conn_update_rtt updates RTT measurements. |rtt| is a latest + * RTT which is not adjusted by ack delay. |ack_delay| is unscaled + * ack_delay included in ACK frame. |ack_delay| is actually tainted + * (sent by peer), so don't assume that |ack_delay| is always smaller + * than, or equals to |rtt|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * RTT sample is ignored. + */ +int ngtcp2_conn_update_rtt(ngtcp2_conn *conn, ngtcp2_duration rtt, + ngtcp2_duration ack_delay, ngtcp2_tstamp ts); + +void ngtcp2_conn_set_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +int ngtcp2_conn_on_loss_detection_timer(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_detect_lost_pkt detects lost packets. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_conn_detect_lost_pkt(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_tx_strmq_top returns the ngtcp2_strm which sits on the + * top of queue. tx_strmq must not be empty. + */ +ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn); + +/* + * ngtcp2_conn_tx_strmq_pop pops the ngtcp2_strm from the queue. + * tx_strmq must not be empty. + */ +void ngtcp2_conn_tx_strmq_pop(ngtcp2_conn *conn); + +/* + * ngtcp2_conn_tx_strmq_push pushes |strm| into tx_strmq. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_conn_tx_strmq_push(ngtcp2_conn *conn, ngtcp2_strm *strm); + +/* + * ngtcp2_conn_internal_expiry returns the minimum expiry time among + * all timers in |conn|. + */ +ngtcp2_tstamp ngtcp2_conn_internal_expiry(ngtcp2_conn *conn); + +ngtcp2_ssize ngtcp2_conn_write_vmsg(ngtcp2_conn *conn, ngtcp2_path *path, + int pkt_info_version, ngtcp2_pkt_info *pi, + uint8_t *dest, size_t destlen, + ngtcp2_vmsg *vmsg, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_write_single_frame_pkt writes a packet which contains + * |fr| frame only in the buffer pointed by |dest| whose length if + * |destlen|. |type| is a long packet type to send. If |type| is 0, + * Short packet is used. |dcid| is used as a destination connection + * ID. |flags| is zero or more of NGTCP2_WRITE_PKT_FLAG_*. Only + * NGTCP2_WRITE_PKT_FLAG_REQUIRE_PADDING is recognized. + * + * The packet written by this function will not be retransmitted. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +ngtcp2_ssize ngtcp2_conn_write_single_frame_pkt( + ngtcp2_conn *conn, ngtcp2_pkt_info *pi, uint8_t *dest, size_t destlen, + uint8_t type, uint8_t flags, const ngtcp2_cid *dcid, ngtcp2_frame *fr, + uint16_t rtb_entry_flags, const ngtcp2_path *path, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_commit_local_transport_params commits the local + * transport parameters, which is currently set to + * conn->local.settings.transport_params. This function will do some + * amends on transport parameters for adjusting default values. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_INVALID_ARGUMENT + * CID in preferred address equals to the original SCID. + */ +int ngtcp2_conn_commit_local_transport_params(ngtcp2_conn *conn); + +/* + * ngtcp2_conn_lost_pkt_expiry returns the earliest expiry time of + * lost packet. + */ +ngtcp2_tstamp ngtcp2_conn_lost_pkt_expiry(ngtcp2_conn *conn); + +/* + * ngtcp2_conn_remove_lost_pkt removes the expired lost packet. + */ +void ngtcp2_conn_remove_lost_pkt(ngtcp2_conn *conn, ngtcp2_tstamp ts); + +/* + * ngtcp2_conn_resched_frames reschedules frames linked from |*pfrc| + * for retransmission. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_conn_resched_frames(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_frame_chain **pfrc); + +uint64_t ngtcp2_conn_tx_strmq_first_cycle(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_ack_delay_expiry` returns the expiry time point of + * delayed protected ACK. One should call + * `ngtcp2_conn_cancel_expired_ack_delay_timer` and + * `ngtcp2_conn_write_pkt` (or `ngtcp2_conn_writev_stream`) when it + * expires. It returns UINT64_MAX if there is no expiry. + */ +ngtcp2_tstamp ngtcp2_conn_ack_delay_expiry(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_cancel_expired_ack_delay_timer` stops expired ACK + * delay timer. |ts| is the current time. This function must be + * called when `ngtcp2_conn_ack_delay_expiry` <= ts. + */ +void ngtcp2_conn_cancel_expired_ack_delay_timer(ngtcp2_conn *conn, + ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_loss_detection_expiry` returns the expiry time point + * of loss detection timer. One should call + * `ngtcp2_conn_on_loss_detection_timer` and `ngtcp2_conn_write_pkt` + * (or `ngtcp2_conn_writev_stream`) when it expires. It returns + * UINT64_MAX if loss detection timer is not armed. + */ +ngtcp2_tstamp ngtcp2_conn_loss_detection_expiry(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_get_idle_expiry` returns the time when a connection + * should be closed if it continues to be idle. If idle timeout is + * disabled, this function returns ``UINT64_MAX``. + */ +ngtcp2_tstamp ngtcp2_conn_get_idle_expiry(ngtcp2_conn *conn); + +ngtcp2_duration ngtcp2_conn_compute_pto(ngtcp2_conn *conn, ngtcp2_pktns *pktns); + +/* + * ngtcp2_conn_track_retired_dcid_seq tracks the sequence number |seq| + * of unacknowledged retiring Destination Connection ID. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_CONNECTION_ID_LIMIT + * The number of unacknowledged retirement exceeds the limit. + */ +int ngtcp2_conn_track_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq); + +/* + * ngtcp2_conn_untrack_retired_dcid_seq deletes the sequence number + * |seq| of unacknowledged retiring Destination Connection ID. It is + * fine if such sequence number is not found. + */ +void ngtcp2_conn_untrack_retired_dcid_seq(ngtcp2_conn *conn, uint64_t seq); + +/* + * ngtcp2_conn_server_negotiate_version negotiates QUIC version. It + * is compatible version negotiation. It returns the negotiated QUIC + * version. This function must not be called by client. + */ +uint32_t +ngtcp2_conn_server_negotiate_version(ngtcp2_conn *conn, + const ngtcp2_version_info *version_info); + +/** + * @function + * + * `ngtcp2_conn_write_connection_close_pkt` writes a packet which + * contains a CONNECTION_CLOSE frame (type 0x1c) in the buffer pointed + * by |dest| whose capacity is |datalen|. + * + * If |path| is not ``NULL``, this function stores the network path + * with which the packet should be sent. Each addr field must point + * to the buffer which should be at least ``sizeof(struct + * sockaddr_storage)`` bytes long. The assignment might not be done + * if nothing is written to |dest|. + * + * If |pi| is not ``NULL``, this function stores packet metadata in it + * if it succeeds. The metadata includes ECN markings. + * + * This function must not be called from inside the callback + * functions. + * + * At the moment, successful call to this function makes connection + * close. We may change this behaviour in the future to allow + * graceful shutdown. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small + * :macro:`NGTCP2_ERR_INVALID_STATE` + * The current state does not allow sending CONNECTION_CLOSE. + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +ngtcp2_ssize ngtcp2_conn_write_connection_close_pkt( + ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint64_t error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts); + +/** + * @function + * + * `ngtcp2_conn_write_application_close_pkt` writes a packet which + * contains a CONNECTION_CLOSE frame (type 0x1d) in the buffer pointed + * by |dest| whose capacity is |datalen|. + * + * If |path| is not ``NULL``, this function stores the network path + * with which the packet should be sent. Each addr field must point + * to the buffer which should be at least ``sizeof(struct + * sockaddr_storage)`` bytes long. The assignment might not be done + * if nothing is written to |dest|. + * + * If |pi| is not ``NULL``, this function stores packet metadata in it + * if it succeeds. The metadata includes ECN markings. + * + * If handshake has not been confirmed yet, CONNECTION_CLOSE (type + * 0x1c) with error code :macro:`NGTCP2_APPLICATION_ERROR` is written + * instead. + * + * This function must not be called from inside the callback + * functions. + * + * At the moment, successful call to this function makes connection + * close. We may change this behaviour in the future to allow + * graceful shutdown. + * + * This function returns the number of bytes written in |dest| if it + * succeeds, or one of the following negative error codes: + * + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory + * :macro:`NGTCP2_ERR_NOBUF` + * Buffer is too small + * :macro:`NGTCP2_ERR_INVALID_STATE` + * The current state does not allow sending CONNECTION_CLOSE. + * :macro:`NGTCP2_ERR_PKT_NUM_EXHAUSTED` + * Packet number is exhausted, and cannot send any more packet. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + */ +ngtcp2_ssize ngtcp2_conn_write_application_close_pkt( + ngtcp2_conn *conn, ngtcp2_path *path, ngtcp2_pkt_info *pi, uint8_t *dest, + size_t destlen, uint64_t app_error_code, const uint8_t *reason, + size_t reasonlen, ngtcp2_tstamp ts); + +int ngtcp2_conn_start_pmtud(ngtcp2_conn *conn); + +void ngtcp2_conn_stop_pmtud(ngtcp2_conn *conn); + +/** + * @function + * + * `ngtcp2_conn_set_remote_transport_params` sets transport parameter + * |params| from a remote endpoint to |conn|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :macro:`NGTCP2_ERR_TRANSPORT_PARAM` + * Failed to validate a remote transport parameters. + * :macro:`NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE` + * Version negotiation failure. + * :macro:`NGTCP2_ERR_CALLBACK_FAILURE` + * User callback failed + * :macro:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +int ngtcp2_conn_set_remote_transport_params( + ngtcp2_conn *conn, const ngtcp2_transport_params *params); + +#endif /* NGTCP2_CONN_H */ diff --git a/lib/ngtcp2_conv.c b/lib/ngtcp2_conv.c new file mode 100644 index 0000000..3367217 --- /dev/null +++ b/lib/ngtcp2_conv.c @@ -0,0 +1,291 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_conv.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_net.h" +#include "ngtcp2_unreachable.h" + +const uint8_t *ngtcp2_get_uint64(uint64_t *dest, const uint8_t *p) { + uint64_t n; + memcpy(&n, p, sizeof(n)); + *dest = ngtcp2_ntohl64(n); + return p + sizeof(n); +} + +const uint8_t *ngtcp2_get_uint48(uint64_t *dest, const uint8_t *p) { + uint64_t n = 0; + memcpy(((uint8_t *)&n) + 2, p, 6); + *dest = ngtcp2_ntohl64(n); + return p + 6; +} + +const uint8_t *ngtcp2_get_uint32(uint32_t *dest, const uint8_t *p) { + uint32_t n; + memcpy(&n, p, sizeof(n)); + *dest = ngtcp2_ntohl(n); + return p + sizeof(n); +} + +const uint8_t *ngtcp2_get_uint24(uint32_t *dest, const uint8_t *p) { + uint32_t n = 0; + memcpy(((uint8_t *)&n) + 1, p, 3); + *dest = ngtcp2_ntohl(n); + return p + 3; +} + +const uint8_t *ngtcp2_get_uint16(uint16_t *dest, const uint8_t *p) { + uint16_t n; + memcpy(&n, p, sizeof(n)); + *dest = ngtcp2_ntohs(n); + return p + sizeof(n); +} + +const uint8_t *ngtcp2_get_uint16be(uint16_t *dest, const uint8_t *p) { + memcpy(dest, p, sizeof(*dest)); + return p + sizeof(*dest); +} + +static uint64_t get_uvarint(size_t *plen, const uint8_t *p) { + union { + uint8_t n8; + uint16_t n16; + uint32_t n32; + uint64_t n64; + } n; + + *plen = (size_t)(1u << (*p >> 6)); + + switch (*plen) { + case 1: + return *p; + case 2: + memcpy(&n, p, 2); + n.n8 &= 0x3f; + return ngtcp2_ntohs(n.n16); + case 4: + memcpy(&n, p, 4); + n.n8 &= 0x3f; + return ngtcp2_ntohl(n.n32); + case 8: + memcpy(&n, p, 8); + n.n8 &= 0x3f; + return ngtcp2_ntohl64(n.n64); + default: + ngtcp2_unreachable(); + } +} + +const uint8_t *ngtcp2_get_uvarint(uint64_t *dest, const uint8_t *p) { + size_t len; + + *dest = get_uvarint(&len, p); + + return p + len; +} + +const uint8_t *ngtcp2_get_varint(int64_t *dest, const uint8_t *p) { + size_t len; + + *dest = (int64_t)get_uvarint(&len, p); + + return p + len; +} + +int64_t ngtcp2_get_pkt_num(const uint8_t *p, size_t pkt_numlen) { + uint32_t l; + uint16_t s; + + switch (pkt_numlen) { + case 1: + return *p; + case 2: + ngtcp2_get_uint16(&s, p); + return (int64_t)s; + case 3: + ngtcp2_get_uint24(&l, p); + return (int64_t)l; + case 4: + ngtcp2_get_uint32(&l, p); + return (int64_t)l; + default: + ngtcp2_unreachable(); + } +} + +uint8_t *ngtcp2_put_uint64be(uint8_t *p, uint64_t n) { + n = ngtcp2_htonl64(n); + return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *ngtcp2_put_uint48be(uint8_t *p, uint64_t n) { + n = ngtcp2_htonl64(n); + return ngtcp2_cpymem(p, ((const uint8_t *)&n) + 2, 6); +} + +uint8_t *ngtcp2_put_uint32be(uint8_t *p, uint32_t n) { + n = ngtcp2_htonl(n); + return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *ngtcp2_put_uint24be(uint8_t *p, uint32_t n) { + n = ngtcp2_htonl(n); + return ngtcp2_cpymem(p, ((const uint8_t *)&n) + 1, 3); +} + +uint8_t *ngtcp2_put_uint16be(uint8_t *p, uint16_t n) { + n = ngtcp2_htons(n); + return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *ngtcp2_put_uint16(uint8_t *p, uint16_t n) { + return ngtcp2_cpymem(p, (const uint8_t *)&n, sizeof(n)); +} + +uint8_t *ngtcp2_put_uvarint(uint8_t *p, uint64_t n) { + uint8_t *rv; + if (n < 64) { + *p++ = (uint8_t)n; + return p; + } + if (n < 16384) { + rv = ngtcp2_put_uint16be(p, (uint16_t)n); + *p |= 0x40; + return rv; + } + if (n < 1073741824) { + rv = ngtcp2_put_uint32be(p, (uint32_t)n); + *p |= 0x80; + return rv; + } + assert(n < 4611686018427387904ULL); + rv = ngtcp2_put_uint64be(p, n); + *p |= 0xc0; + return rv; +} + +uint8_t *ngtcp2_put_uvarint30(uint8_t *p, uint32_t n) { + uint8_t *rv; + + assert(n < 1073741824); + + rv = ngtcp2_put_uint32be(p, n); + *p |= 0x80; + + return rv; +} + +uint8_t *ngtcp2_put_pkt_num(uint8_t *p, int64_t pkt_num, size_t len) { + switch (len) { + case 1: + *p++ = (uint8_t)pkt_num; + return p; + case 2: + ngtcp2_put_uint16be(p, (uint16_t)pkt_num); + return p + 2; + case 3: + ngtcp2_put_uint24be(p, (uint32_t)pkt_num); + return p + 3; + case 4: + ngtcp2_put_uint32be(p, (uint32_t)pkt_num); + return p + 4; + default: + ngtcp2_unreachable(); + } +} + +size_t ngtcp2_get_uvarintlen(const uint8_t *p) { + return (size_t)(1u << (*p >> 6)); +} + +size_t ngtcp2_put_uvarintlen(uint64_t n) { + if (n < 64) { + return 1; + } + if (n < 16384) { + return 2; + } + if (n < 1073741824) { + return 4; + } + assert(n < 4611686018427387904ULL); + return 8; +} + +int64_t ngtcp2_nth_server_bidi_id(uint64_t n) { + if (n == 0) { + return 0; + } + + if ((NGTCP2_MAX_VARINT >> 2) < n - 1) { + return NGTCP2_MAX_SERVER_STREAM_ID_BIDI; + } + + return (int64_t)(((n - 1) << 2) | 0x01); +} + +int64_t ngtcp2_nth_client_bidi_id(uint64_t n) { + if (n == 0) { + return 0; + } + + if ((NGTCP2_MAX_VARINT >> 2) < n - 1) { + return NGTCP2_MAX_CLIENT_STREAM_ID_BIDI; + } + + return (int64_t)((n - 1) << 2); +} + +int64_t ngtcp2_nth_server_uni_id(uint64_t n) { + if (n == 0) { + return 0; + } + + if ((NGTCP2_MAX_VARINT >> 2) < n - 1) { + return NGTCP2_MAX_SERVER_STREAM_ID_UNI; + } + + return (int64_t)(((n - 1) << 2) | 0x03); +} + +int64_t ngtcp2_nth_client_uni_id(uint64_t n) { + if (n == 0) { + return 0; + } + + if ((NGTCP2_MAX_VARINT >> 2) < n - 1) { + return NGTCP2_MAX_CLIENT_STREAM_ID_UNI; + } + + return (int64_t)(((n - 1) << 2) | 0x02); +} + +uint64_t ngtcp2_ord_stream_id(int64_t stream_id) { + return (uint64_t)(stream_id >> 2) + 1; +} diff --git a/lib/ngtcp2_conv.h b/lib/ngtcp2_conv.h new file mode 100644 index 0000000..ef089a9 --- /dev/null +++ b/lib/ngtcp2_conv.h @@ -0,0 +1,208 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CONV_H +#define NGTCP2_CONV_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * ngtcp2_get_uint64 reads 8 bytes from |p| as 64 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 8. + */ +const uint8_t *ngtcp2_get_uint64(uint64_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint48 reads 6 bytes from |p| as 48 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 6. + */ +const uint8_t *ngtcp2_get_uint48(uint64_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint32 reads 4 bytes from |p| as 32 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 4. + */ +const uint8_t *ngtcp2_get_uint32(uint32_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint24 reads 3 bytes from |p| as 24 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 3. + */ +const uint8_t *ngtcp2_get_uint24(uint32_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint16 reads 2 bytes from |p| as 16 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| in host byte order. It returns |p| + 2. + */ +const uint8_t *ngtcp2_get_uint16(uint16_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uint16be reads 2 bytes from |p| as 16 bits unsigned + * integer encoded as network byte order, and stores it in the buffer + * pointed by |dest| as is. It returns |p| + 2. + */ +const uint8_t *ngtcp2_get_uint16be(uint16_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_uvarint reads variable-length unsigned integer from |p|, + * and stores it in the buffer pointed by |dest| in host byte order. + * It returns |p| plus the number of bytes read from |p|. + */ +const uint8_t *ngtcp2_get_uvarint(uint64_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_varint reads variable-length unsigned integer from |p|, + * and casts it to the signed integer, and stores it in the buffer + * pointed by |dest| in host byte order. No information should be + * lost in this cast, because the variable-length integer is 62 + * bits. It returns |p| plus the number of bytes read from |p|. + */ +const uint8_t *ngtcp2_get_varint(int64_t *dest, const uint8_t *p); + +/* + * ngtcp2_get_pkt_num reads encoded packet number from |p|. The + * packet number is encoed in |pkt_numlen| bytes. + */ +int64_t ngtcp2_get_pkt_num(const uint8_t *p, size_t pkt_numlen); + +/* + * ngtcp2_put_uint64be writes |n| in host byte order in |p| in network + * byte order. It returns the one beyond of the last written + * position. + */ +uint8_t *ngtcp2_put_uint64be(uint8_t *p, uint64_t n); + +/* + * ngtcp2_put_uint48be writes |n| in host byte order in |p| in network + * byte order. It writes only least significant 48 bits. It returns + * the one beyond of the last written position. + */ +uint8_t *ngtcp2_put_uint48be(uint8_t *p, uint64_t n); + +/* + * ngtcp2_put_uint32be writes |n| in host byte order in |p| in network + * byte order. It returns the one beyond of the last written + * position. + */ +uint8_t *ngtcp2_put_uint32be(uint8_t *p, uint32_t n); + +/* + * ngtcp2_put_uint24be writes |n| in host byte order in |p| in network + * byte order. It writes only least significant 24 bits. It returns + * the one beyond of the last written position. + */ +uint8_t *ngtcp2_put_uint24be(uint8_t *p, uint32_t n); + +/* + * ngtcp2_put_uint16be writes |n| in host byte order in |p| in network + * byte order. It returns the one beyond of the last written + * position. + */ +uint8_t *ngtcp2_put_uint16be(uint8_t *p, uint16_t n); + +/* + * ngtcp2_put_uint16 writes |n| as is in |p|. It returns the one + * beyond of the last written position. + */ +uint8_t *ngtcp2_put_uint16(uint8_t *p, uint16_t n); + +/* + * ngtcp2_put_uvarint writes |n| in |p| using variable-length integer + * encoding. It returns the one beyond of the last written position. + */ +uint8_t *ngtcp2_put_uvarint(uint8_t *p, uint64_t n); + +/* + * ngtcp2_put_uvarint30 writes |n| in |p| using variable-length + * integer encoding. |n| must be strictly less than 1073741824. The + * function always encodes |n| in 4 bytes. It returns the one beyond + * of the last written position. + */ +uint8_t *ngtcp2_put_uvarint30(uint8_t *p, uint32_t n); + +/* + * ngtcp2_put_pkt_num encodes |pkt_num| using |len| bytes. It + * returns the one beyond of the last written position. + */ +uint8_t *ngtcp2_put_pkt_num(uint8_t *p, int64_t pkt_num, size_t len); + +/* + * ngtcp2_get_uvarintlen returns the required number of bytes to read + * variable-length integer starting at |p|. + */ +size_t ngtcp2_get_uvarintlen(const uint8_t *p); + +/* + * ngtcp2_put_uvarintlen returns the required number of bytes to + * encode |n|. + */ +size_t ngtcp2_put_uvarintlen(uint64_t n); + +/* + * ngtcp2_nth_server_bidi_id returns |n|-th server bidirectional + * stream ID. If |n| is 0, it returns 0. If the |n|-th stream ID is + * larger than NGTCP2_MAX_SERVER_STREAM_ID_BIDI, this function returns + * NGTCP2_MAX_SERVER_STREAM_ID_BIDI. + */ +int64_t ngtcp2_nth_server_bidi_id(uint64_t n); + +/* + * ngtcp2_nth_client_bidi_id returns |n|-th client bidirectional + * stream ID. If |n| is 0, it returns 0. If the |n|-th stream ID is + * larger than NGTCP2_MAX_CLIENT_STREAM_ID_BIDI, this function returns + * NGTCP2_MAX_CLIENT_STREAM_ID_BIDI. + */ +int64_t ngtcp2_nth_client_bidi_id(uint64_t n); + +/* + * ngtcp2_nth_server_uni_id returns |n|-th server unidirectional + * stream ID. If |n| is 0, it returns 0. If the |n|-th stream ID is + * larger than NGTCP2_MAX_SERVER_STREAM_ID_UNI, this function returns + * NGTCP2_MAX_SERVER_STREAM_ID_UNI. + */ +int64_t ngtcp2_nth_server_uni_id(uint64_t n); + +/* + * ngtcp2_nth_client_uni_id returns |n|-th client unidirectional + * stream ID. If |n| is 0, it returns 0. If the |n|-th stream ID is + * larger than NGTCP2_MAX_CLIENT_STREAM_ID_UNI, this function returns + * NGTCP2_MAX_CLIENT_STREAM_ID_UNI. + */ +int64_t ngtcp2_nth_client_uni_id(uint64_t n); + +/* + * ngtcp2_ord_stream_id returns the ordinal number of |stream_id|. + */ +uint64_t ngtcp2_ord_stream_id(int64_t stream_id); + +#endif /* NGTCP2_CONV_H */ diff --git a/lib/ngtcp2_crypto.c b/lib/ngtcp2_crypto.c new file mode 100644 index 0000000..5e69a82 --- /dev/null +++ b/lib/ngtcp2_crypto.c @@ -0,0 +1,895 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_crypto.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_conn.h" +#include "ngtcp2_net.h" + +int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret, + size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_mem *mem) { + int rv = ngtcp2_crypto_km_nocopy_new(pckm, secretlen, ivlen, mem); + if (rv != 0) { + return rv; + } + + if (secretlen) { + memcpy((*pckm)->secret.base, secret, secretlen); + } + if (aead_ctx) { + (*pckm)->aead_ctx = *aead_ctx; + } + memcpy((*pckm)->iv.base, iv, ivlen); + + return 0; +} + +int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen, + size_t ivlen, const ngtcp2_mem *mem) { + size_t len; + uint8_t *p; + + len = sizeof(ngtcp2_crypto_km) + secretlen + ivlen; + + *pckm = ngtcp2_mem_malloc(mem, len); + if (*pckm == NULL) { + return NGTCP2_ERR_NOMEM; + } + + p = (uint8_t *)(*pckm) + sizeof(ngtcp2_crypto_km); + (*pckm)->secret.base = p; + (*pckm)->secret.len = secretlen; + p += secretlen; + (*pckm)->iv.base = p; + (*pckm)->iv.len = ivlen; + (*pckm)->aead_ctx.native_handle = NULL; + (*pckm)->pkt_num = -1; + (*pckm)->use_count = 0; + (*pckm)->flags = NGTCP2_CRYPTO_KM_FLAG_NONE; + + return 0; +} + +void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, const ngtcp2_mem *mem) { + if (ckm == NULL) { + return; + } + + ngtcp2_mem_free(mem, ckm); +} + +void ngtcp2_crypto_create_nonce(uint8_t *dest, const uint8_t *iv, size_t ivlen, + int64_t pkt_num) { + size_t i; + uint64_t n; + + assert(ivlen >= 8); + + memcpy(dest, iv, ivlen); + n = ngtcp2_htonl64((uint64_t)pkt_num); + + for (i = 0; i < 8; ++i) { + dest[ivlen - 8 + i] ^= ((uint8_t *)&n)[i]; + } +} + +/* + * varint_paramlen returns the length of a single transport parameter + * which has variable integer in its parameter. + */ +static size_t varint_paramlen(ngtcp2_transport_param_id id, uint64_t param) { + size_t valuelen = ngtcp2_put_uvarintlen(param); + return ngtcp2_put_uvarintlen(id) + ngtcp2_put_uvarintlen(valuelen) + valuelen; +} + +/* + * write_varint_param writes parameter |id| of the given |value| in + * varint encoding. It returns p + the number of bytes written. + */ +static uint8_t *write_varint_param(uint8_t *p, ngtcp2_transport_param_id id, + uint64_t value) { + p = ngtcp2_put_uvarint(p, id); + p = ngtcp2_put_uvarint(p, ngtcp2_put_uvarintlen(value)); + return ngtcp2_put_uvarint(p, value); +} + +/* + * cid_paramlen returns the length of a single transport parameter + * which has |cid| as value. + */ +static size_t cid_paramlen(ngtcp2_transport_param_id id, + const ngtcp2_cid *cid) { + return ngtcp2_put_uvarintlen(id) + ngtcp2_put_uvarintlen(cid->datalen) + + cid->datalen; +} + +/* + * write_cid_param writes parameter |id| of the given |cid|. It + * returns p + the number of bytes written. + */ +static uint8_t *write_cid_param(uint8_t *p, ngtcp2_transport_param_id id, + const ngtcp2_cid *cid) { + assert(cid->datalen == 0 || cid->datalen >= NGTCP2_MIN_CIDLEN); + assert(cid->datalen <= NGTCP2_MAX_CIDLEN); + + p = ngtcp2_put_uvarint(p, id); + p = ngtcp2_put_uvarint(p, cid->datalen); + if (cid->datalen) { + p = ngtcp2_cpymem(p, cid->data, cid->datalen); + } + return p; +} + +static const uint8_t empty_address[16]; + +ngtcp2_ssize ngtcp2_encode_transport_params_versioned( + uint8_t *dest, size_t destlen, ngtcp2_transport_params_type exttype, + int transport_params_version, const ngtcp2_transport_params *params) { + uint8_t *p; + size_t len = 0; + /* For some reason, gcc 7.3.0 requires this initialization. */ + size_t preferred_addrlen = 0; + size_t version_infolen = 0; + const ngtcp2_sockaddr_in *sa_in; + const ngtcp2_sockaddr_in6 *sa_in6; + (void)transport_params_version; + + switch (exttype) { + case NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO: + break; + case NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS: + len += + cid_paramlen(NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID, + ¶ms->original_dcid); + + if (params->stateless_reset_token_present) { + len += + ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN) + + ngtcp2_put_uvarintlen(NGTCP2_STATELESS_RESET_TOKENLEN) + + NGTCP2_STATELESS_RESET_TOKENLEN; + } + if (params->preferred_address_present) { + assert(params->preferred_address.cid.datalen >= NGTCP2_MIN_CIDLEN); + assert(params->preferred_address.cid.datalen <= NGTCP2_MAX_CIDLEN); + preferred_addrlen = 4 /* ipv4Address */ + 2 /* ipv4Port */ + + 16 /* ipv6Address */ + 2 /* ipv6Port */ + + 1 + + params->preferred_address.cid.datalen /* CID */ + + NGTCP2_STATELESS_RESET_TOKENLEN; + len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS) + + ngtcp2_put_uvarintlen(preferred_addrlen) + preferred_addrlen; + } + if (params->retry_scid_present) { + len += cid_paramlen(NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID, + ¶ms->retry_scid); + } + break; + default: + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + len += cid_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID, + ¶ms->initial_scid); + + if (params->initial_max_stream_data_bidi_local) { + len += varint_paramlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + params->initial_max_stream_data_bidi_local); + } + if (params->initial_max_stream_data_bidi_remote) { + len += varint_paramlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + params->initial_max_stream_data_bidi_remote); + } + if (params->initial_max_stream_data_uni) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI, + params->initial_max_stream_data_uni); + } + if (params->initial_max_data) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA, + params->initial_max_data); + } + if (params->initial_max_streams_bidi) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI, + params->initial_max_streams_bidi); + } + if (params->initial_max_streams_uni) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI, + params->initial_max_streams_uni); + } + if (params->max_udp_payload_size != + NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE, + params->max_udp_payload_size); + } + if (params->ack_delay_exponent != NGTCP2_DEFAULT_ACK_DELAY_EXPONENT) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT, + params->ack_delay_exponent); + } + if (params->disable_active_migration) { + len += + ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION) + + ngtcp2_put_uvarintlen(0); + } + if (params->max_ack_delay != NGTCP2_DEFAULT_MAX_ACK_DELAY) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY, + params->max_ack_delay / NGTCP2_MILLISECONDS); + } + if (params->max_idle_timeout) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT, + params->max_idle_timeout / NGTCP2_MILLISECONDS); + } + if (params->active_connection_id_limit && + params->active_connection_id_limit != + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT, + params->active_connection_id_limit); + } + if (params->max_datagram_frame_size) { + len += varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE, + params->max_datagram_frame_size); + } + if (params->grease_quic_bit) { + len += ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT) + + ngtcp2_put_uvarintlen(0); + } + if (params->version_info_present) { + version_infolen = sizeof(uint32_t) + params->version_info.other_versionslen; + len += ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION_DRAFT) + + ngtcp2_put_uvarintlen(version_infolen) + version_infolen; + } + + if (dest == NULL && destlen == 0) { + return (ngtcp2_ssize)len; + } + + if (destlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = dest; + + if (exttype == NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS) { + p = write_cid_param( + p, NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID, + ¶ms->original_dcid); + + if (params->stateless_reset_token_present) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN); + p = ngtcp2_put_uvarint(p, sizeof(params->stateless_reset_token)); + p = ngtcp2_cpymem(p, params->stateless_reset_token, + sizeof(params->stateless_reset_token)); + } + if (params->preferred_address_present) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS); + p = ngtcp2_put_uvarint(p, preferred_addrlen); + + if (params->preferred_address.ipv4_present) { + sa_in = ¶ms->preferred_address.ipv4; + p = ngtcp2_cpymem(p, &sa_in->sin_addr, sizeof(sa_in->sin_addr)); + p = ngtcp2_put_uint16(p, sa_in->sin_port); + } else { + p = ngtcp2_cpymem(p, empty_address, sizeof(sa_in->sin_addr)); + p = ngtcp2_put_uint16(p, 0); + } + + if (params->preferred_address.ipv6_present) { + sa_in6 = ¶ms->preferred_address.ipv6; + p = ngtcp2_cpymem(p, &sa_in6->sin6_addr, sizeof(sa_in6->sin6_addr)); + p = ngtcp2_put_uint16(p, sa_in6->sin6_port); + } else { + p = ngtcp2_cpymem(p, empty_address, sizeof(sa_in6->sin6_addr)); + p = ngtcp2_put_uint16(p, 0); + } + + *p++ = (uint8_t)params->preferred_address.cid.datalen; + if (params->preferred_address.cid.datalen) { + p = ngtcp2_cpymem(p, params->preferred_address.cid.data, + params->preferred_address.cid.datalen); + } + p = ngtcp2_cpymem( + p, params->preferred_address.stateless_reset_token, + sizeof(params->preferred_address.stateless_reset_token)); + } + if (params->retry_scid_present) { + p = write_cid_param(p, NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID, + ¶ms->retry_scid); + } + } + + p = write_cid_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID, + ¶ms->initial_scid); + + if (params->initial_max_stream_data_bidi_local) { + p = write_varint_param( + p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + params->initial_max_stream_data_bidi_local); + } + + if (params->initial_max_stream_data_bidi_remote) { + p = write_varint_param( + p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + params->initial_max_stream_data_bidi_remote); + } + + if (params->initial_max_stream_data_uni) { + p = write_varint_param(p, + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI, + params->initial_max_stream_data_uni); + } + + if (params->initial_max_data) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA, + params->initial_max_data); + } + + if (params->initial_max_streams_bidi) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI, + params->initial_max_streams_bidi); + } + + if (params->initial_max_streams_uni) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI, + params->initial_max_streams_uni); + } + + if (params->max_udp_payload_size != + NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE, + params->max_udp_payload_size); + } + + if (params->ack_delay_exponent != NGTCP2_DEFAULT_ACK_DELAY_EXPONENT) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT, + params->ack_delay_exponent); + } + + if (params->disable_active_migration) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION); + p = ngtcp2_put_uvarint(p, 0); + } + + if (params->max_ack_delay != NGTCP2_DEFAULT_MAX_ACK_DELAY) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY, + params->max_ack_delay / NGTCP2_MILLISECONDS); + } + + if (params->max_idle_timeout) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT, + params->max_idle_timeout / NGTCP2_MILLISECONDS); + } + + if (params->active_connection_id_limit && + params->active_connection_id_limit != + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT, + params->active_connection_id_limit); + } + + if (params->max_datagram_frame_size) { + p = write_varint_param(p, NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE, + params->max_datagram_frame_size); + } + + if (params->grease_quic_bit) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT); + p = ngtcp2_put_uvarint(p, 0); + } + + if (params->version_info_present) { + p = ngtcp2_put_uvarint(p, NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION_DRAFT); + p = ngtcp2_put_uvarint(p, version_infolen); + p = ngtcp2_put_uint32be(p, params->version_info.chosen_version); + if (params->version_info.other_versionslen) { + p = ngtcp2_cpymem(p, params->version_info.other_versions, + params->version_info.other_versionslen); + } + } + + assert((size_t)(p - dest) == len); + + return (ngtcp2_ssize)len; +} + +/* + * decode_varint decodes a single varint from the buffer pointed by + * |*pp| of length |end - *pp|. If it decodes an integer + * successfully, it stores the integer in |*pdest|, increment |*pp| by + * the number of bytes read from |*pp|, and returns 0. Otherwise it + * returns -1. + */ +static int decode_varint(uint64_t *pdest, const uint8_t **pp, + const uint8_t *end) { + const uint8_t *p = *pp; + size_t len; + + if (p == end) { + return -1; + } + + len = ngtcp2_get_uvarintlen(p); + if ((uint64_t)(end - p) < len) { + return -1; + } + + *pp = ngtcp2_get_uvarint(pdest, p); + + return 0; +} + +/* + * decode_varint_param decodes length prefixed value from the buffer + * pointed by |*pp| of length |end - *pp|. The length and value are + * encoded in varint form. If it decodes a value successfully, it + * stores the value in |*pdest|, increment |*pp| by the number of + * bytes read from |*pp|, and returns 0. Otherwise it returns -1. + */ +static int decode_varint_param(uint64_t *pdest, const uint8_t **pp, + const uint8_t *end) { + const uint8_t *p = *pp; + uint64_t valuelen; + + if (decode_varint(&valuelen, &p, end) != 0) { + return -1; + } + + if (p == end) { + return -1; + } + + if ((uint64_t)(end - p) < valuelen) { + return -1; + } + + if (ngtcp2_get_uvarintlen(p) != valuelen) { + return -1; + } + + *pp = ngtcp2_get_uvarint(pdest, p); + + return 0; +} + +/* + * decode_cid_param decodes length prefixed ngtcp2_cid from the buffer + * pointed by |*pp| of length |end - *pp|. The length is encoded in + * varint form. If it decodes a value successfully, it stores the + * value in |*pdest|, increment |*pp| by the number of read from + * |*pp|, and returns the number of bytes read. Otherwise it returns + * the one of the negative error code: + * + * NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM + * Could not decode Connection ID. + */ +static int decode_cid_param(ngtcp2_cid *pdest, const uint8_t **pp, + const uint8_t *end) { + const uint8_t *p = *pp; + uint64_t valuelen; + + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + if ((valuelen != 0 && valuelen < NGTCP2_MIN_CIDLEN) || + valuelen > NGTCP2_MAX_CIDLEN || (size_t)(end - p) < valuelen) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + ngtcp2_cid_init(pdest, p, (size_t)valuelen); + + p += valuelen; + + *pp = p; + + return 0; +} + +int ngtcp2_decode_transport_params_versioned( + int transport_params_version, ngtcp2_transport_params *params, + ngtcp2_transport_params_type exttype, const uint8_t *data, size_t datalen) { + const uint8_t *p, *end, *lend; + size_t len; + uint64_t param_type; + uint64_t valuelen; + int rv; + int initial_scid_present = 0; + int original_dcid_present = 0; + ngtcp2_sockaddr_in *sa_in; + ngtcp2_sockaddr_in6 *sa_in6; + uint32_t version; + + (void)transport_params_version; + + if (datalen == 0) { + return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM; + } + + /* Set default values */ + memset(params, 0, sizeof(*params)); + params->initial_max_streams_bidi = 0; + params->initial_max_streams_uni = 0; + params->initial_max_stream_data_bidi_local = 0; + params->initial_max_stream_data_bidi_remote = 0; + params->initial_max_stream_data_uni = 0; + params->max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + params->ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + params->stateless_reset_token_present = 0; + params->preferred_address_present = 0; + params->disable_active_migration = 0; + params->max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; + params->max_idle_timeout = 0; + params->active_connection_id_limit = + NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params->retry_scid_present = 0; + params->max_datagram_frame_size = 0; + memset(¶ms->retry_scid, 0, sizeof(params->retry_scid)); + memset(¶ms->initial_scid, 0, sizeof(params->initial_scid)); + memset(¶ms->original_dcid, 0, sizeof(params->original_dcid)); + params->version_info_present = 0; + + p = data; + end = data + datalen; + + for (; (size_t)(end - p) >= 2;) { + if (decode_varint(¶m_type, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + switch (param_type) { + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + if (decode_varint_param(¶ms->initial_max_stream_data_bidi_local, &p, + end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + if (decode_varint_param(¶ms->initial_max_stream_data_bidi_remote, &p, + end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI: + if (decode_varint_param(¶ms->initial_max_stream_data_uni, &p, end) != + 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA: + if (decode_varint_param(¶ms->initial_max_data, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI: + if (decode_varint_param(¶ms->initial_max_streams_bidi, &p, end) != + 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->initial_max_streams_bidi > NGTCP2_MAX_STREAMS) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI: + if (decode_varint_param(¶ms->initial_max_streams_uni, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->initial_max_streams_uni > NGTCP2_MAX_STREAMS) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT: + if (decode_varint_param(¶ms->max_idle_timeout, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + params->max_idle_timeout *= NGTCP2_MILLISECONDS; + break; + case NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE: + if (decode_varint_param(¶ms->max_udp_payload_size, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN: + if (exttype != NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)valuelen != sizeof(params->stateless_reset_token)) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)(end - p) < sizeof(params->stateless_reset_token)) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + p = ngtcp2_get_bytes(params->stateless_reset_token, p, + sizeof(params->stateless_reset_token)); + params->stateless_reset_token_present = 1; + + break; + case NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT: + if (decode_varint_param(¶ms->ack_delay_exponent, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->ack_delay_exponent > 20) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS: + if (exttype != NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)(end - p) < valuelen) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + len = 4 /* ipv4Address */ + 2 /* ipv4Port */ + 16 /* ipv6Address */ + + 2 /* ipv6Port */ + + 1 /* cid length */ + NGTCP2_STATELESS_RESET_TOKENLEN; + if (valuelen < len) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + sa_in = ¶ms->preferred_address.ipv4; + + p = ngtcp2_get_bytes(&sa_in->sin_addr, p, sizeof(sa_in->sin_addr)); + p = ngtcp2_get_uint16be(&sa_in->sin_port, p); + + if (sa_in->sin_port || memcmp(empty_address, &sa_in->sin_addr, + sizeof(sa_in->sin_addr)) != 0) { + sa_in->sin_family = AF_INET; + params->preferred_address.ipv4_present = 1; + } + + sa_in6 = ¶ms->preferred_address.ipv6; + + p = ngtcp2_get_bytes(&sa_in6->sin6_addr, p, sizeof(sa_in6->sin6_addr)); + p = ngtcp2_get_uint16be(&sa_in6->sin6_port, p); + + if (sa_in6->sin6_port || memcmp(empty_address, &sa_in6->sin6_addr, + sizeof(sa_in6->sin6_addr)) != 0) { + sa_in6->sin6_family = AF_INET6; + params->preferred_address.ipv6_present = 1; + } + + /* cid */ + params->preferred_address.cid.datalen = *p++; + len += params->preferred_address.cid.datalen; + if (valuelen != len || + params->preferred_address.cid.datalen > NGTCP2_MAX_CIDLEN || + params->preferred_address.cid.datalen < NGTCP2_MIN_CIDLEN) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->preferred_address.cid.datalen) { + p = ngtcp2_get_bytes(params->preferred_address.cid.data, p, + params->preferred_address.cid.datalen); + } + + /* stateless reset token */ + p = ngtcp2_get_bytes( + params->preferred_address.stateless_reset_token, p, + sizeof(params->preferred_address.stateless_reset_token)); + params->preferred_address_present = 1; + break; + case NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION: + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (valuelen != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + params->disable_active_migration = 1; + break; + case NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID: + if (exttype != NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + rv = decode_cid_param(¶ms->original_dcid, &p, end); + if (rv != 0) { + return rv; + } + original_dcid_present = 1; + break; + case NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID: + if (exttype != NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + rv = decode_cid_param(¶ms->retry_scid, &p, end); + if (rv != 0) { + return rv; + } + params->retry_scid_present = 1; + break; + case NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID: + rv = decode_cid_param(¶ms->initial_scid, &p, end); + if (rv != 0) { + return rv; + } + initial_scid_present = 1; + break; + case NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY: + if (decode_varint_param(¶ms->max_ack_delay, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (params->max_ack_delay >= 16384) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + params->max_ack_delay *= NGTCP2_MILLISECONDS; + break; + case NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT: + if (decode_varint_param(¶ms->active_connection_id_limit, &p, end) != + 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE: + if (decode_varint_param(¶ms->max_datagram_frame_size, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + break; + case NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT: + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (valuelen != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + params->grease_quic_bit = 1; + break; + case NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION_DRAFT: + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)(end - p) < valuelen) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (valuelen < sizeof(uint32_t) || (valuelen & 0x3)) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + p = ngtcp2_get_uint32(¶ms->version_info.chosen_version, p); + if (params->version_info.chosen_version == 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if (valuelen > sizeof(uint32_t)) { + params->version_info.other_versions = (uint8_t *)p; + params->version_info.other_versionslen = + (size_t)valuelen - sizeof(uint32_t); + + for (lend = p + (valuelen - sizeof(uint32_t)); p != lend;) { + p = ngtcp2_get_uint32(&version, p); + if (version == 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + } + } + params->version_info_present = 1; + break; + default: + /* Ignore unknown parameter */ + if (decode_varint(&valuelen, &p, end) != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + if ((size_t)(end - p) < valuelen) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + p += valuelen; + break; + } + } + + if (end - p != 0) { + return NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM; + } + + if (!initial_scid_present || + (exttype == NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS && + !original_dcid_present)) { + return NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM; + } + + return 0; +} + +static int transport_params_copy_new(ngtcp2_transport_params **pdest, + const ngtcp2_transport_params *src, + const ngtcp2_mem *mem) { + size_t len = sizeof(**pdest); + ngtcp2_transport_params *dest; + uint8_t *p; + + if (src->version_info_present) { + len += src->version_info.other_versionslen; + } + + dest = ngtcp2_mem_malloc(mem, len); + if (dest == NULL) { + return NGTCP2_ERR_NOMEM; + } + + *dest = *src; + + if (src->version_info_present && src->version_info.other_versionslen) { + p = (uint8_t *)dest + sizeof(*dest); + memcpy(p, src->version_info.other_versions, + src->version_info.other_versionslen); + dest->version_info.other_versions = p; + } + + *pdest = dest; + + return 0; +} + +int ngtcp2_decode_transport_params_new(ngtcp2_transport_params **pparams, + ngtcp2_transport_params_type exttype, + const uint8_t *data, size_t datalen, + const ngtcp2_mem *mem) { + int rv; + ngtcp2_transport_params params; + + rv = ngtcp2_decode_transport_params(¶ms, exttype, data, datalen); + if (rv < 0) { + return rv; + } + + if (mem == NULL) { + mem = ngtcp2_mem_default(); + } + + return transport_params_copy_new(pparams, ¶ms, mem); +} + +void ngtcp2_transport_params_del(ngtcp2_transport_params *params, + const ngtcp2_mem *mem) { + if (params == NULL) { + return; + } + + if (mem == NULL) { + mem = ngtcp2_mem_default(); + } + + ngtcp2_mem_free(mem, params); +} + +int ngtcp2_transport_params_copy_new(ngtcp2_transport_params **pdest, + const ngtcp2_transport_params *src, + const ngtcp2_mem *mem) { + if (src == NULL) { + *pdest = NULL; + return 0; + } + + return transport_params_copy_new(pdest, src, mem); +} diff --git a/lib/ngtcp2_crypto.h b/lib/ngtcp2_crypto.h new file mode 100644 index 0000000..9a9d95f --- /dev/null +++ b/lib/ngtcp2_crypto.h @@ -0,0 +1,147 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CRYPTO_H +#define NGTCP2_CRYPTO_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" + +/* NGTCP2_INITIAL_AEAD_OVERHEAD is an overhead of AEAD used by Initial + packets. Because QUIC uses AEAD_AES_128_GCM, the overhead is 16 + bytes. */ +#define NGTCP2_INITIAL_AEAD_OVERHEAD 16 + +/* NGTCP2_MAX_AEAD_OVERHEAD is expected maximum AEAD overhead. */ +#define NGTCP2_MAX_AEAD_OVERHEAD 16 + +/* ngtcp2_transport_param_id is the registry of QUIC transport + parameter ID. */ +typedef enum ngtcp2_transport_param_id { + NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID = 0x0000, + NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT = 0x0001, + NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN = 0x0002, + NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE = 0x0003, + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA = 0x0004, + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL = 0x0005, + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE = 0x0006, + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI = 0x0007, + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI = 0x0008, + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI = 0x0009, + NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT = 0x000a, + NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY = 0x000b, + NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION = 0x000c, + NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS = 0x000d, + NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT = 0x000e, + NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID = 0x000f, + NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID = 0x0010, + /* https://datatracker.ietf.org/doc/html/rfc9221 */ + NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE = 0x0020, + NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT = 0x2ab2, + /* https://quicwg.org/quic-v2/draft-ietf-quic-v2.html */ + NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION_DRAFT = 0xff73db, +} ngtcp2_transport_param_id; + +/* NGTCP2_CRYPTO_KM_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_CRYPTO_KM_FLAG_NONE 0x00u +/* NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE is set if key phase bit is + set. */ +#define NGTCP2_CRYPTO_KM_FLAG_KEY_PHASE_ONE 0x01u + +typedef struct ngtcp2_crypto_km { + ngtcp2_vec secret; + ngtcp2_crypto_aead_ctx aead_ctx; + ngtcp2_vec iv; + /* pkt_num is a packet number of a packet which uses this keying + material. For encryption key, it is the lowest packet number of + a packet. For decryption key, it is the lowest packet number of + a packet which can be decrypted with this keying material. */ + int64_t pkt_num; + /* use_count is the number of encryption applied with this key. + This field is only used for tx key. */ + uint64_t use_count; + /* flags is the bitwise OR of zero or more of + NGTCP2_CRYPTO_KM_FLAG_*. */ + uint8_t flags; +} ngtcp2_crypto_km; + +/* + * ngtcp2_crypto_km_new creates new ngtcp2_crypto_km object and + * assigns its pointer to |*pckm|. The |secret| of length + * |secretlen|, the |key| of length |keylen| and the |iv| of length + * |ivlen| are copied to |*pckm|. If |secretlen| == 0, the function + * assumes no secret is given which is acceptable. The sole reason to + * store secret is update keys. Only 1RTT key can be updated. + */ +int ngtcp2_crypto_km_new(ngtcp2_crypto_km **pckm, const uint8_t *secret, + size_t secretlen, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *iv, size_t ivlen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_crypto_km_nocopy_new is similar to ngtcp2_crypto_km_new, but + * it does not copy secret, key and IV. + */ +int ngtcp2_crypto_km_nocopy_new(ngtcp2_crypto_km **pckm, size_t secretlen, + size_t ivlen, const ngtcp2_mem *mem); + +void ngtcp2_crypto_km_del(ngtcp2_crypto_km *ckm, const ngtcp2_mem *mem); + +typedef struct ngtcp2_crypto_cc { + ngtcp2_crypto_aead aead; + ngtcp2_crypto_cipher hp; + ngtcp2_crypto_km *ckm; + ngtcp2_crypto_cipher_ctx hp_ctx; + ngtcp2_encrypt encrypt; + ngtcp2_decrypt decrypt; + ngtcp2_hp_mask hp_mask; +} ngtcp2_crypto_cc; + +void ngtcp2_crypto_create_nonce(uint8_t *dest, const uint8_t *iv, size_t ivlen, + int64_t pkt_num); + +/* + * ngtcp2_transport_params_copy_new makes a copy of |src|, and assigns + * it to |*pdest|. If |src| is NULL, NULL is assigned to |*pdest|. + * + * Caller is responsible to call ngtcp2_transport_params_del to free + * the memory assigned to |*pdest|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_transport_params_copy_new(ngtcp2_transport_params **pdest, + const ngtcp2_transport_params *src, + const ngtcp2_mem *mem); + +#endif /* NGTCP2_CRYPTO_H */ diff --git a/lib/ngtcp2_err.c b/lib/ngtcp2_err.c new file mode 100644 index 0000000..8f676da --- /dev/null +++ b/lib/ngtcp2_err.c @@ -0,0 +1,154 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_err.h" + +const char *ngtcp2_strerror(int liberr) { + switch (liberr) { + case 0: + return "NO_ERROR"; + case NGTCP2_ERR_INVALID_ARGUMENT: + return "ERR_INVALID_ARGUMENT"; + case NGTCP2_ERR_NOBUF: + return "ERR_NOBUF"; + case NGTCP2_ERR_PROTO: + return "ERR_PROTO"; + case NGTCP2_ERR_INVALID_STATE: + return "ERR_INVALID_STATE"; + case NGTCP2_ERR_ACK_FRAME: + return "ERR_ACK_FRAME"; + case NGTCP2_ERR_STREAM_ID_BLOCKED: + return "ERR_STREAM_ID_BLOCKED"; + case NGTCP2_ERR_STREAM_IN_USE: + return "ERR_STREAM_IN_USE"; + case NGTCP2_ERR_STREAM_DATA_BLOCKED: + return "ERR_STREAM_DATA_BLOCKED"; + case NGTCP2_ERR_FLOW_CONTROL: + return "ERR_FLOW_CONTROL"; + case NGTCP2_ERR_CONNECTION_ID_LIMIT: + return "ERR_CONNECTION_ID_LIMIT"; + case NGTCP2_ERR_STREAM_LIMIT: + return "ERR_STREAM_LIMIT"; + case NGTCP2_ERR_FINAL_SIZE: + return "ERR_FINAL_SIZE"; + case NGTCP2_ERR_CRYPTO: + return "ERR_CRYPTO"; + case NGTCP2_ERR_PKT_NUM_EXHAUSTED: + return "ERR_PKT_NUM_EXHAUSTED"; + case NGTCP2_ERR_NOMEM: + return "ERR_NOMEM"; + case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: + return "ERR_REQUIRED_TRANSPORT_PARAM"; + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + return "ERR_MALFORMED_TRANSPORT_PARAM"; + case NGTCP2_ERR_FRAME_ENCODING: + return "ERR_FRAME_ENCODING"; + case NGTCP2_ERR_DECRYPT: + return "ERR_DECRYPT"; + case NGTCP2_ERR_STREAM_SHUT_WR: + return "ERR_STREAM_SHUT_WR"; + case NGTCP2_ERR_STREAM_NOT_FOUND: + return "ERR_STREAM_NOT_FOUND"; + case NGTCP2_ERR_STREAM_STATE: + return "ERR_STREAM_STATE"; + case NGTCP2_ERR_RECV_VERSION_NEGOTIATION: + return "ERR_RECV_VERSION_NEGOTIATION"; + case NGTCP2_ERR_CLOSING: + return "ERR_CLOSING"; + case NGTCP2_ERR_DRAINING: + return "ERR_DRAINING"; + case NGTCP2_ERR_TRANSPORT_PARAM: + return "ERR_TRANSPORT_PARAM"; + case NGTCP2_ERR_DISCARD_PKT: + return "ERR_DISCARD_PKT"; + case NGTCP2_ERR_CONN_ID_BLOCKED: + return "ERR_CONN_ID_BLOCKED"; + case NGTCP2_ERR_CALLBACK_FAILURE: + return "ERR_CALLBACK_FAILURE"; + case NGTCP2_ERR_INTERNAL: + return "ERR_INTERNAL"; + case NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED: + return "ERR_CRYPTO_BUFFER_EXCEEDED"; + case NGTCP2_ERR_WRITE_MORE: + return "ERR_WRITE_MORE"; + case NGTCP2_ERR_RETRY: + return "ERR_RETRY"; + case NGTCP2_ERR_DROP_CONN: + return "ERR_DROP_CONN"; + case NGTCP2_ERR_AEAD_LIMIT_REACHED: + return "ERR_AEAD_LIMIT_REACHED"; + case NGTCP2_ERR_NO_VIABLE_PATH: + return "ERR_NO_VIABLE_PATH"; + case NGTCP2_ERR_VERSION_NEGOTIATION: + return "ERR_VERSION_NEGOTIATION"; + case NGTCP2_ERR_HANDSHAKE_TIMEOUT: + return "ERR_HANDSHAKE_TIMEOUT"; + case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE: + return "ERR_VERSION_NEGOTIATION_FAILURE"; + case NGTCP2_ERR_IDLE_CLOSE: + return "ERR_IDLE_CLOSE"; + default: + return "(unknown)"; + } +} + +int ngtcp2_err_is_fatal(int liberr) { return liberr < NGTCP2_ERR_FATAL; } + +uint64_t ngtcp2_err_infer_quic_transport_error_code(int liberr) { + switch (liberr) { + case 0: + return NGTCP2_NO_ERROR; + case NGTCP2_ERR_ACK_FRAME: + case NGTCP2_ERR_FRAME_ENCODING: + return NGTCP2_FRAME_ENCODING_ERROR; + case NGTCP2_ERR_FLOW_CONTROL: + return NGTCP2_FLOW_CONTROL_ERROR; + case NGTCP2_ERR_CONNECTION_ID_LIMIT: + return NGTCP2_CONNECTION_ID_LIMIT_ERROR; + case NGTCP2_ERR_STREAM_LIMIT: + return NGTCP2_STREAM_LIMIT_ERROR; + case NGTCP2_ERR_FINAL_SIZE: + return NGTCP2_FINAL_SIZE_ERROR; + case NGTCP2_ERR_REQUIRED_TRANSPORT_PARAM: + case NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM: + case NGTCP2_ERR_TRANSPORT_PARAM: + return NGTCP2_TRANSPORT_PARAMETER_ERROR; + case NGTCP2_ERR_INVALID_ARGUMENT: + case NGTCP2_ERR_NOMEM: + case NGTCP2_ERR_CALLBACK_FAILURE: + return NGTCP2_INTERNAL_ERROR; + case NGTCP2_ERR_STREAM_STATE: + return NGTCP2_STREAM_STATE_ERROR; + case NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED: + return NGTCP2_CRYPTO_BUFFER_EXCEEDED; + case NGTCP2_ERR_AEAD_LIMIT_REACHED: + return NGTCP2_AEAD_LIMIT_REACHED; + case NGTCP2_ERR_NO_VIABLE_PATH: + return NGTCP2_NO_VIABLE_PATH; + case NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE: + return NGTCP2_VERSION_NEGOTIATION_ERROR_DRAFT; + default: + return NGTCP2_PROTOCOL_VIOLATION; + } +} diff --git a/lib/ngtcp2_err.h b/lib/ngtcp2_err.h new file mode 100644 index 0000000..9229f54 --- /dev/null +++ b/lib/ngtcp2_err.h @@ -0,0 +1,34 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_ERR_H +#define NGTCP2_ERR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#endif /* NGTCP2_ERR_H */ diff --git a/lib/ngtcp2_gaptr.c b/lib/ngtcp2_gaptr.c new file mode 100644 index 0000000..87c2389 --- /dev/null +++ b/lib/ngtcp2_gaptr.c @@ -0,0 +1,167 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_gaptr.h" + +#include <string.h> +#include <assert.h> + +void ngtcp2_gaptr_init(ngtcp2_gaptr *gaptr, const ngtcp2_mem *mem) { + ngtcp2_ksl_init(&gaptr->gap, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), + mem); + + gaptr->mem = mem; +} + +static int gaptr_gap_init(ngtcp2_gaptr *gaptr) { + ngtcp2_range range = {0, UINT64_MAX}; + int rv; + + rv = ngtcp2_ksl_insert(&gaptr->gap, NULL, &range, NULL); + if (rv != 0) { + return rv; + } + + return 0; +} + +void ngtcp2_gaptr_free(ngtcp2_gaptr *gaptr) { + if (gaptr == NULL) { + return; + } + + ngtcp2_ksl_free(&gaptr->gap); +} + +int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, uint64_t datalen) { + int rv; + ngtcp2_range k, m, l, r, q = {offset, offset + datalen}; + ngtcp2_ksl_it it; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + rv = gaptr_gap_init(gaptr); + if (rv != 0) { + return rv; + } + } + + it = ngtcp2_ksl_lower_bound_compar(&gaptr->gap, &q, + ngtcp2_ksl_range_exclusive_compar); + + for (; !ngtcp2_ksl_it_end(&it);) { + k = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + m = ngtcp2_range_intersect(&q, &k); + if (!ngtcp2_range_len(&m)) { + break; + } + + if (ngtcp2_range_eq(&k, &m)) { + ngtcp2_ksl_remove_hint(&gaptr->gap, &it, &it, &k); + continue; + } + ngtcp2_range_cut(&l, &r, &k, &m); + if (ngtcp2_range_len(&l)) { + ngtcp2_ksl_update_key(&gaptr->gap, &k, &l); + + if (ngtcp2_range_len(&r)) { + rv = ngtcp2_ksl_insert(&gaptr->gap, &it, &r, NULL); + if (rv != 0) { + return rv; + } + } + } else if (ngtcp2_range_len(&r)) { + ngtcp2_ksl_update_key(&gaptr->gap, &k, &r); + } + ngtcp2_ksl_it_next(&it); + } + return 0; +} + +uint64_t ngtcp2_gaptr_first_gap_offset(ngtcp2_gaptr *gaptr) { + ngtcp2_ksl_it it; + ngtcp2_range r; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + return 0; + } + + it = ngtcp2_ksl_begin(&gaptr->gap); + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + return r.begin; +} + +ngtcp2_range ngtcp2_gaptr_get_first_gap_after(ngtcp2_gaptr *gaptr, + uint64_t offset) { + ngtcp2_range q = {offset, offset + 1}; + ngtcp2_ksl_it it; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + ngtcp2_range r = {0, UINT64_MAX}; + return r; + } + + it = ngtcp2_ksl_lower_bound_compar(&gaptr->gap, &q, + ngtcp2_ksl_range_exclusive_compar); + + assert(!ngtcp2_ksl_it_end(&it)); + + return *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); +} + +int ngtcp2_gaptr_is_pushed(ngtcp2_gaptr *gaptr, uint64_t offset, + uint64_t datalen) { + ngtcp2_range q = {offset, offset + datalen}; + ngtcp2_ksl_it it; + ngtcp2_range k; + ngtcp2_range m; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + return 0; + } + + it = ngtcp2_ksl_lower_bound_compar(&gaptr->gap, &q, + ngtcp2_ksl_range_exclusive_compar); + k = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + m = ngtcp2_range_intersect(&q, &k); + + return ngtcp2_range_len(&m) == 0; +} + +void ngtcp2_gaptr_drop_first_gap(ngtcp2_gaptr *gaptr) { + ngtcp2_ksl_it it; + ngtcp2_range r; + + if (ngtcp2_ksl_len(&gaptr->gap) == 0) { + return; + } + + it = ngtcp2_ksl_begin(&gaptr->gap); + + assert(!ngtcp2_ksl_it_end(&it)); + + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + ngtcp2_ksl_remove_hint(&gaptr->gap, NULL, &it, &r); +} diff --git a/lib/ngtcp2_gaptr.h b/lib/ngtcp2_gaptr.h new file mode 100644 index 0000000..0f100a8 --- /dev/null +++ b/lib/ngtcp2_gaptr.h @@ -0,0 +1,98 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_GAPTR_H +#define NGTCP2_GAPTR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_ksl.h" +#include "ngtcp2_range.h" + +/* + * ngtcp2_gaptr maintains the gap in the range [0, UINT64_MAX). + */ +typedef struct ngtcp2_gaptr { + /* gap maintains the range of offset which is not received + yet. Initially, its range is [0, UINT64_MAX). */ + ngtcp2_ksl gap; + /* mem is custom memory allocator */ + const ngtcp2_mem *mem; +} ngtcp2_gaptr; + +/* + * ngtcp2_gaptr_init initializes |gaptr|. + */ +void ngtcp2_gaptr_init(ngtcp2_gaptr *gaptr, const ngtcp2_mem *mem); + +/* + * ngtcp2_gaptr_free frees resources allocated for |gaptr|. + */ +void ngtcp2_gaptr_free(ngtcp2_gaptr *gaptr); + +/* + * ngtcp2_gaptr_push 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: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_gaptr_push(ngtcp2_gaptr *gaptr, uint64_t offset, uint64_t datalen); + +/* + * ngtcp2_gaptr_first_gap_offset returns the offset to the first gap. + * If there is no gap, it returns UINT64_MAX. + */ +uint64_t ngtcp2_gaptr_first_gap_offset(ngtcp2_gaptr *gaptr); + +/* + * ngtcp2_gaptr_get_first_gap_after returns the first gap which + * overlaps or comes after |offset|. + */ +ngtcp2_range ngtcp2_gaptr_get_first_gap_after(ngtcp2_gaptr *gaptr, + uint64_t offset); + +/* + * ngtcp2_gaptr_is_pushed returns nonzero if range [offset, offset + + * datalen) is completely pushed into this object. + */ +int ngtcp2_gaptr_is_pushed(ngtcp2_gaptr *gaptr, uint64_t offset, + uint64_t datalen); + +/* + * ngtcp2_gaptr_drop_first_gap deletes the first gap entirely as if + * the range is pushed. This function assumes that at least one gap + * exists. + */ +void ngtcp2_gaptr_drop_first_gap(ngtcp2_gaptr *gaptr); + +#endif /* NGTCP2_GAPTR_H */ diff --git a/lib/ngtcp2_idtr.c b/lib/ngtcp2_idtr.c new file mode 100644 index 0000000..d988022 --- /dev/null +++ b/lib/ngtcp2_idtr.c @@ -0,0 +1,79 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_idtr.h" + +#include <assert.h> + +void ngtcp2_idtr_init(ngtcp2_idtr *idtr, int server, const ngtcp2_mem *mem) { + ngtcp2_gaptr_init(&idtr->gap, mem); + + idtr->server = server; +} + +void ngtcp2_idtr_free(ngtcp2_idtr *idtr) { + if (idtr == NULL) { + return; + } + + ngtcp2_gaptr_free(&idtr->gap); +} + +/* + * id_from_stream_id translates |stream_id| to id space used by + * ngtcp2_idtr. + */ +static uint64_t id_from_stream_id(int64_t stream_id) { + return (uint64_t)(stream_id >> 2); +} + +int ngtcp2_idtr_open(ngtcp2_idtr *idtr, int64_t stream_id) { + uint64_t q; + + assert((idtr->server && (stream_id % 2)) || + (!idtr->server && (stream_id % 2)) == 0); + + q = id_from_stream_id(stream_id); + + if (ngtcp2_gaptr_is_pushed(&idtr->gap, q, 1)) { + return NGTCP2_ERR_STREAM_IN_USE; + } + + return ngtcp2_gaptr_push(&idtr->gap, q, 1); +} + +int ngtcp2_idtr_is_open(ngtcp2_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 ngtcp2_gaptr_is_pushed(&idtr->gap, q, 1); +} + +uint64_t ngtcp2_idtr_first_gap(ngtcp2_idtr *idtr) { + return ngtcp2_gaptr_first_gap_offset(&idtr->gap); +} diff --git a/lib/ngtcp2_idtr.h b/lib/ngtcp2_idtr.h new file mode 100644 index 0000000..edb8c68 --- /dev/null +++ b/lib/ngtcp2_idtr.h @@ -0,0 +1,89 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_IDTR_H +#define NGTCP2_IDTR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_gaptr.h" + +/* + * ngtcp2_idtr tracks the usage of stream ID. + */ +typedef struct ngtcp2_idtr { + /* gap maintains the range of ID which is not used yet. Initially, + its range is [0, UINT64_MAX). */ + ngtcp2_gaptr gap; + /* server is nonzero if this object records server initiated stream + ID. */ + int server; +} ngtcp2_idtr; + +/* + * ngtcp2_idtr_init initializes |idtr|. + * + * If this object records server initiated ID (even number), set + * |server| to nonzero. + */ +void ngtcp2_idtr_init(ngtcp2_idtr *idtr, int server, const ngtcp2_mem *mem); + +/* + * ngtcp2_idtr_free frees resources allocated for |idtr|. + */ +void ngtcp2_idtr_free(ngtcp2_idtr *idtr); + +/* + * ngtcp2_idtr_open claims that |stream_id| is in used. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_STREAM_IN_USE + * ID has already been used. + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_idtr_open(ngtcp2_idtr *idtr, int64_t stream_id); + +/* + * ngtcp2_idtr_open tells whether ID |stream_id| is in used or not. + * + * It returns nonzero if |stream_id| is used. + */ +int ngtcp2_idtr_is_open(ngtcp2_idtr *idtr, int64_t stream_id); + +/* + * ngtcp2_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 ngtcp2_idtr_first_gap(ngtcp2_idtr *idtr); + +#endif /* NGTCP2_IDTR_H */ diff --git a/lib/ngtcp2_ksl.c b/lib/ngtcp2_ksl.c new file mode 100644 index 0000000..0bd424c --- /dev/null +++ b/lib/ngtcp2_ksl.c @@ -0,0 +1,819 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_ksl.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "ngtcp2_macro.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_range.h" + +static ngtcp2_ksl_blk null_blk = {{{NULL, NULL, 0, 0, {0}}}}; + +static size_t ksl_nodelen(size_t keylen) { + return (sizeof(ngtcp2_ksl_node) + keylen - sizeof(uint64_t) + 0xfu) & + ~(uintptr_t)0xfu; +} + +static size_t ksl_blklen(size_t nodelen) { + return sizeof(ngtcp2_ksl_blk) + nodelen * NGTCP2_KSL_MAX_NBLK - + sizeof(uint64_t); +} + +/* + * ksl_node_set_key sets |key| to |node|. + */ +static void ksl_node_set_key(ngtcp2_ksl *ksl, ngtcp2_ksl_node *node, + const void *key) { + memcpy(node->key, key, ksl->keylen); +} + +void ngtcp2_ksl_init(ngtcp2_ksl *ksl, ngtcp2_ksl_compar compar, size_t keylen, + const ngtcp2_mem *mem) { + size_t nodelen = ksl_nodelen(keylen); + + ngtcp2_objalloc_init(&ksl->blkalloc, + ((ksl_blklen(nodelen) + 0xfu) & ~(uintptr_t)0xfu) * 8, + mem); + + ksl->head = NULL; + ksl->front = ksl->back = NULL; + ksl->compar = compar; + ksl->keylen = keylen; + ksl->nodelen = nodelen; + ksl->n = 0; +} + +static ngtcp2_ksl_blk *ksl_blk_objalloc_new(ngtcp2_ksl *ksl) { + return ngtcp2_objalloc_ksl_blk_len_get(&ksl->blkalloc, + ksl_blklen(ksl->nodelen)); +} + +static void ksl_blk_objalloc_del(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + ngtcp2_objalloc_ksl_blk_release(&ksl->blkalloc, blk); +} + +static int ksl_head_init(ngtcp2_ksl *ksl) { + ngtcp2_ksl_blk *head = ksl_blk_objalloc_new(ksl); + if (!head) { + return NGTCP2_ERR_NOMEM; + } + + head->next = head->prev = NULL; + head->n = 0; + head->leaf = 1; + + ksl->head = head; + ksl->front = ksl->back = head; + + return 0; +} + +#ifdef NOMEMPOOL +/* + * ksl_free_blk frees |blk| recursively. + */ +static void ksl_free_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + size_t i; + + if (!blk->leaf) { + for (i = 0; i < blk->n; ++i) { + ksl_free_blk(ksl, ngtcp2_ksl_nth_node(ksl, blk, i)->blk); + } + } + + ksl_blk_objalloc_del(ksl, blk); +} +#endif /* NOMEMPOOL */ + +void ngtcp2_ksl_free(ngtcp2_ksl *ksl) { + if (!ksl || !ksl->head) { + return; + } + +#ifdef NOMEMPOOL + ksl_free_blk(ksl, ksl->head); +#endif /* NOMEMPOOL */ + + ngtcp2_objalloc_free(&ksl->blkalloc); +} + +/* + * ksl_split_blk splits |blk| into 2 ngtcp2_ksl_blk objects. The new + * ngtcp2_ksl_blk is always the "right" block. + * + * It returns the pointer to the ngtcp2_ksl_blk created which is the + * located at the right of |blk|, or NULL which indicates out of + * memory error. + */ +static ngtcp2_ksl_blk *ksl_split_blk(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk) { + ngtcp2_ksl_blk *rblk; + + rblk = ksl_blk_objalloc_new(ksl); + if (rblk == NULL) { + return NULL; + } + + rblk->next = blk->next; + blk->next = rblk; + if (rblk->next) { + rblk->next->prev = rblk; + } else if (ksl->back == blk) { + ksl->back = rblk; + } + rblk->prev = blk; + rblk->leaf = blk->leaf; + + rblk->n = blk->n / 2; + + memcpy(rblk->nodes, blk->nodes + ksl->nodelen * (blk->n - rblk->n), + ksl->nodelen * rblk->n); + + blk->n -= rblk->n; + + assert(blk->n >= NGTCP2_KSL_MIN_NBLK); + assert(rblk->n >= NGTCP2_KSL_MIN_NBLK); + + return rblk; +} + +/* + * ksl_split_node splits a node included in |blk| at the position |i| + * into 2 adjacent nodes. The new node is always inserted at the + * position |i+1|. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int ksl_split_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + ngtcp2_ksl_node *node; + ngtcp2_ksl_blk *lblk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk, *rblk; + + rblk = ksl_split_blk(ksl, lblk); + if (rblk == NULL) { + return NGTCP2_ERR_NOMEM; + } + + memmove(blk->nodes + (i + 2) * ksl->nodelen, + blk->nodes + (i + 1) * ksl->nodelen, + ksl->nodelen * (blk->n - (i + 1))); + + node = ngtcp2_ksl_nth_node(ksl, blk, i + 1); + node->blk = rblk; + ++blk->n; + ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, rblk, rblk->n - 1)->key); + + node = ngtcp2_ksl_nth_node(ksl, blk, i); + ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); + + return 0; +} + +/* + * ksl_split_head splits a head (root) block. It increases the height + * of skip list by 1. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +static int ksl_split_head(ngtcp2_ksl *ksl) { + ngtcp2_ksl_blk *rblk = NULL, *lblk, *nhead = NULL; + ngtcp2_ksl_node *node; + + rblk = ksl_split_blk(ksl, ksl->head); + if (rblk == NULL) { + return NGTCP2_ERR_NOMEM; + } + + lblk = ksl->head; + + nhead = ksl_blk_objalloc_new(ksl); + if (nhead == NULL) { + ksl_blk_objalloc_del(ksl, rblk); + return NGTCP2_ERR_NOMEM; + } + nhead->next = nhead->prev = NULL; + nhead->n = 2; + nhead->leaf = 0; + + node = ngtcp2_ksl_nth_node(ksl, nhead, 0); + ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); + node->blk = lblk; + + node = ngtcp2_ksl_nth_node(ksl, nhead, 1); + ksl_node_set_key(ksl, node, ngtcp2_ksl_nth_node(ksl, rblk, rblk->n - 1)->key); + node->blk = rblk; + + ksl->head = nhead; + + return 0; +} + +/* + * insert_node inserts a node whose key is |key| with the associated + * |data| at the index of |i|. This function assumes that the number + * of nodes contained by |blk| is strictly less than + * NGTCP2_KSL_MAX_NBLK. + */ +static void ksl_insert_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i, + const ngtcp2_ksl_key *key, void *data) { + ngtcp2_ksl_node *node; + + assert(blk->n < NGTCP2_KSL_MAX_NBLK); + + memmove(blk->nodes + (i + 1) * ksl->nodelen, blk->nodes + i * ksl->nodelen, + ksl->nodelen * (blk->n - i)); + + node = ngtcp2_ksl_nth_node(ksl, blk, i); + ksl_node_set_key(ksl, node, key); + node->data = data; + + ++blk->n; +} + +static size_t ksl_bsearch(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + const ngtcp2_ksl_key *key, ngtcp2_ksl_compar compar) { + size_t i; + ngtcp2_ksl_node *node; + + for (i = 0, node = (ngtcp2_ksl_node *)(void *)blk->nodes; + i < blk->n && compar((ngtcp2_ksl_key *)node->key, key); + ++i, node = (ngtcp2_ksl_node *)(void *)((uint8_t *)node + ksl->nodelen)) + ; + + return i; +} + +int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key, void *data) { + ngtcp2_ksl_blk *blk; + ngtcp2_ksl_node *node; + size_t i; + int rv; + + if (!ksl->head) { + rv = ksl_head_init(ksl); + if (rv != 0) { + return rv; + } + } + + blk = ksl->head; + + if (blk->n == NGTCP2_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, ngtcp2_ksl_nth_node(ksl, blk, i)->key)) { + if (it) { + *it = ngtcp2_ksl_end(ksl); + } + return NGTCP2_ERR_INVALID_ARGUMENT; + } + ksl_insert_node(ksl, blk, i, key, data); + ++ksl->n; + if (it) { + ngtcp2_ksl_it_init(it, ksl, blk, i); + } + return 0; + } + + if (i == blk->n) { + /* This insertion extends the largest key in this subtree. */ + for (; !blk->leaf;) { + node = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1); + if (node->blk->n == NGTCP2_KSL_MAX_NBLK) { + rv = ksl_split_node(ksl, blk, blk->n - 1); + if (rv != 0) { + return rv; + } + node = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1); + } + ksl_node_set_key(ksl, node, key); + blk = node->blk; + } + ksl_insert_node(ksl, blk, blk->n, key, data); + ++ksl->n; + if (it) { + ngtcp2_ksl_it_init(it, ksl, blk, blk->n - 1); + } + return 0; + } + + node = ngtcp2_ksl_nth_node(ksl, blk, i); + + if (node->blk->n == NGTCP2_KSL_MAX_NBLK) { + rv = ksl_split_node(ksl, blk, i); + if (rv != 0) { + return rv; + } + if (ksl->compar((ngtcp2_ksl_key *)node->key, key)) { + node = ngtcp2_ksl_nth_node(ksl, blk, i + 1); + if (ksl->compar((ngtcp2_ksl_key *)node->key, key)) { + ksl_node_set_key(ksl, node, key); + } + } + } + + blk = node->blk; + } +} + +/* + * ksl_remove_node removes the node included in |blk| at the index of + * |i|. + */ +static void ksl_remove_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + memmove(blk->nodes + i * ksl->nodelen, blk->nodes + (i + 1) * ksl->nodelen, + ksl->nodelen * (blk->n - (i + 1))); + + --blk->n; +} + +/* + * ksl_merge_node merges 2 nodes which are the nodes at the index of + * |i| and |i + 1|. + * + * If |blk| is the 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 ngtcp2_ksl_blk *ksl_merge_node(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, + size_t i) { + ngtcp2_ksl_blk *lblk, *rblk; + + assert(i + 1 < blk->n); + + lblk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk; + rblk = ngtcp2_ksl_nth_node(ksl, blk, i + 1)->blk; + + assert(lblk->n + rblk->n < NGTCP2_KSL_MAX_NBLK); + + memcpy(lblk->nodes + ksl->nodelen * lblk->n, rblk->nodes, + ksl->nodelen * rblk->n); + + lblk->n += rblk->n; + lblk->next = rblk->next; + if (lblk->next) { + lblk->next->prev = lblk; + } else if (ksl->back == rblk) { + ksl->back = lblk; + } + + ksl_blk_objalloc_del(ksl, rblk); + + if (ksl->head == blk && blk->n == 2) { + ksl_blk_objalloc_del(ksl, ksl->head); + ksl->head = lblk; + } else { + ksl_remove_node(ksl, blk, i + 1); + ksl_node_set_key(ksl, ngtcp2_ksl_nth_node(ksl, blk, i), + ngtcp2_ksl_nth_node(ksl, lblk, lblk->n - 1)->key); + } + + return lblk; +} + +/* + * ksl_shift_left moves the first nodes in blk->nodes[i]->blk->nodes + * to blk->nodes[i - 1]->blk->nodes in a manner that they have the + * same amount of nodes as much as possible. + */ +static void ksl_shift_left(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + ngtcp2_ksl_node *lnode, *rnode; + size_t n; + + assert(i > 0); + + lnode = ngtcp2_ksl_nth_node(ksl, blk, i - 1); + rnode = ngtcp2_ksl_nth_node(ksl, blk, i); + + assert(lnode->blk->n < NGTCP2_KSL_MAX_NBLK); + assert(rnode->blk->n > NGTCP2_KSL_MIN_NBLK); + + n = (lnode->blk->n + rnode->blk->n + 1) / 2 - lnode->blk->n; + + assert(n > 0); + assert(lnode->blk->n <= NGTCP2_KSL_MAX_NBLK - n); + assert(rnode->blk->n >= NGTCP2_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, ngtcp2_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(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t i) { + ngtcp2_ksl_node *lnode, *rnode; + size_t n; + + assert(i < blk->n - 1); + + lnode = ngtcp2_ksl_nth_node(ksl, blk, i); + rnode = ngtcp2_ksl_nth_node(ksl, blk, i + 1); + + assert(lnode->blk->n > NGTCP2_KSL_MIN_NBLK); + assert(rnode->blk->n < NGTCP2_KSL_MAX_NBLK); + + n = (lnode->blk->n + rnode->blk->n + 1) / 2 - rnode->blk->n; + + assert(n > 0); + assert(lnode->blk->n >= NGTCP2_KSL_MIN_NBLK + n); + assert(rnode->blk->n <= NGTCP2_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, ngtcp2_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(ngtcp2_ksl_compar compar, const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + return !compar(lhs, rhs) && !compar(rhs, lhs); +} + +int ngtcp2_ksl_remove_hint(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_it *hint, + const ngtcp2_ksl_key *key) { + ngtcp2_ksl_blk *blk = hint->blk; + + assert(ksl->head); + + if (blk->n <= NGTCP2_KSL_MIN_NBLK) { + return ngtcp2_ksl_remove(ksl, it, key); + } + + ksl_remove_node(ksl, blk, hint->i); + + --ksl->n; + + if (it) { + if (hint->i == blk->n && blk->next) { + ngtcp2_ksl_it_init(it, ksl, blk->next, 0); + } else { + ngtcp2_ksl_it_init(it, ksl, blk, hint->i); + } + } + + return 0; +} + +int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key) { + ngtcp2_ksl_blk *blk = ksl->head; + ngtcp2_ksl_node *node; + size_t i; + + if (!ksl->head) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (!blk->leaf && blk->n == 2 && + ngtcp2_ksl_nth_node(ksl, blk, 0)->blk->n == NGTCP2_KSL_MIN_NBLK && + ngtcp2_ksl_nth_node(ksl, blk, 1)->blk->n == NGTCP2_KSL_MIN_NBLK) { + blk = ksl_merge_node(ksl, ksl->head, 0); + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, ksl->compar); + + if (i == blk->n) { + if (it) { + *it = ngtcp2_ksl_end(ksl); + } + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (blk->leaf) { + if (ksl->compar(key, ngtcp2_ksl_nth_node(ksl, blk, i)->key)) { + if (it) { + *it = ngtcp2_ksl_end(ksl); + } + return NGTCP2_ERR_INVALID_ARGUMENT; + } + ksl_remove_node(ksl, blk, i); + --ksl->n; + if (it) { + if (blk->n == i && blk->next) { + ngtcp2_ksl_it_init(it, ksl, blk->next, 0); + } else { + ngtcp2_ksl_it_init(it, ksl, blk, i); + } + } + return 0; + } + + node = ngtcp2_ksl_nth_node(ksl, blk, i); + + if (node->blk->n > NGTCP2_KSL_MIN_NBLK) { + blk = node->blk; + continue; + } + + assert(node->blk->n == NGTCP2_KSL_MIN_NBLK); + + if (i + 1 < blk->n && + ngtcp2_ksl_nth_node(ksl, blk, i + 1)->blk->n > NGTCP2_KSL_MIN_NBLK) { + ksl_shift_left(ksl, blk, i + 1); + blk = node->blk; + continue; + } + + if (i > 0 && + ngtcp2_ksl_nth_node(ksl, blk, i - 1)->blk->n > NGTCP2_KSL_MIN_NBLK) { + ksl_shift_right(ksl, blk, i - 1); + blk = node->blk; + continue; + } + + if (i + 1 < blk->n) { + blk = ksl_merge_node(ksl, blk, i); + continue; + } + + assert(i > 0); + + blk = ksl_merge_node(ksl, blk, i - 1); + } +} + +ngtcp2_ksl_it ngtcp2_ksl_lower_bound(ngtcp2_ksl *ksl, + const ngtcp2_ksl_key *key) { + ngtcp2_ksl_blk *blk = ksl->head; + ngtcp2_ksl_it it; + size_t i; + + if (!blk) { + ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); + return it; + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, ksl->compar); + + if (blk->leaf) { + if (i == blk->n && blk->next) { + blk = blk->next; + i = 0; + } + ngtcp2_ksl_it_init(&it, ksl, blk, i); + return it; + } + + if (i == blk->n) { + /* This happens if descendant has smaller key. Fast forward to + find last node in this subtree. */ + for (; !blk->leaf; blk = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1)->blk) + ; + if (blk->next) { + blk = blk->next; + i = 0; + } else { + i = blk->n; + } + ngtcp2_ksl_it_init(&it, ksl, blk, i); + return it; + } + blk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk; + } +} + +ngtcp2_ksl_it ngtcp2_ksl_lower_bound_compar(ngtcp2_ksl *ksl, + const ngtcp2_ksl_key *key, + ngtcp2_ksl_compar compar) { + ngtcp2_ksl_blk *blk = ksl->head; + ngtcp2_ksl_it it; + size_t i; + + if (!blk) { + ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); + return it; + } + + for (;;) { + i = ksl_bsearch(ksl, blk, key, compar); + + if (blk->leaf) { + if (i == blk->n && blk->next) { + blk = blk->next; + i = 0; + } + ngtcp2_ksl_it_init(&it, ksl, blk, i); + return it; + } + + if (i == blk->n) { + /* This happens if descendant has smaller key. Fast forward to + find last node in this subtree. */ + for (; !blk->leaf; blk = ngtcp2_ksl_nth_node(ksl, blk, blk->n - 1)->blk) + ; + if (blk->next) { + blk = blk->next; + i = 0; + } else { + i = blk->n; + } + ngtcp2_ksl_it_init(&it, ksl, blk, i); + return it; + } + blk = ngtcp2_ksl_nth_node(ksl, blk, i)->blk; + } +} + +void ngtcp2_ksl_update_key(ngtcp2_ksl *ksl, const ngtcp2_ksl_key *old_key, + const ngtcp2_ksl_key *new_key) { + ngtcp2_ksl_blk *blk = ksl->head; + ngtcp2_ksl_node *node; + size_t i; + + assert(ksl->head); + + for (;;) { + i = ksl_bsearch(ksl, blk, old_key, ksl->compar); + + assert(i < blk->n); + node = ngtcp2_ksl_nth_node(ksl, blk, i); + + if (blk->leaf) { + assert(key_equal(ksl->compar, (ngtcp2_ksl_key *)node->key, old_key)); + ksl_node_set_key(ksl, node, new_key); + return; + } + + if (key_equal(ksl->compar, (ngtcp2_ksl_key *)node->key, old_key) || + ksl->compar((ngtcp2_ksl_key *)node->key, new_key)) { + ksl_node_set_key(ksl, node, new_key); + } + + blk = node->blk; + } +} + +static void ksl_print(ngtcp2_ksl *ksl, ngtcp2_ksl_blk *blk, size_t level) { + size_t i; + ngtcp2_ksl_node *node; + + fprintf(stderr, "LV=%zu n=%u\n", level, blk->n); + + if (blk->leaf) { + for (i = 0; i < blk->n; ++i) { + node = ngtcp2_ksl_nth_node(ksl, blk, i); + fprintf(stderr, " %" PRId64, *(int64_t *)(void *)node->key); + } + fprintf(stderr, "\n"); + return; + } + + for (i = 0; i < blk->n; ++i) { + ksl_print(ksl, ngtcp2_ksl_nth_node(ksl, blk, i)->blk, level + 1); + } +} + +size_t ngtcp2_ksl_len(ngtcp2_ksl *ksl) { return ksl->n; } + +void ngtcp2_ksl_clear(ngtcp2_ksl *ksl) { + if (!ksl->head) { + return; + } + +#ifdef NOMEMPOOL + ksl_free_blk(ksl, ksl->head); +#endif /* NOMEMPOOL */ + + ksl->front = ksl->back = ksl->head = NULL; + ksl->n = 0; + + ngtcp2_objalloc_clear(&ksl->blkalloc); +} + +void ngtcp2_ksl_print(ngtcp2_ksl *ksl) { + if (!ksl->head) { + return; + } + + ksl_print(ksl, ksl->head, 0); +} + +ngtcp2_ksl_it ngtcp2_ksl_begin(const ngtcp2_ksl *ksl) { + ngtcp2_ksl_it it; + + if (ksl->head) { + ngtcp2_ksl_it_init(&it, ksl, ksl->front, 0); + } else { + ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); + } + + return it; +} + +ngtcp2_ksl_it ngtcp2_ksl_end(const ngtcp2_ksl *ksl) { + ngtcp2_ksl_it it; + + if (ksl->head) { + ngtcp2_ksl_it_init(&it, ksl, ksl->back, ksl->back->n); + } else { + ngtcp2_ksl_it_init(&it, ksl, &null_blk, 0); + } + + return it; +} + +void ngtcp2_ksl_it_init(ngtcp2_ksl_it *it, const ngtcp2_ksl *ksl, + ngtcp2_ksl_blk *blk, size_t i) { + it->ksl = ksl; + it->blk = blk; + it->i = i; +} + +void ngtcp2_ksl_it_prev(ngtcp2_ksl_it *it) { + assert(!ngtcp2_ksl_it_begin(it)); + + if (it->i == 0) { + it->blk = it->blk->prev; + it->i = it->blk->n - 1; + } else { + --it->i; + } +} + +int ngtcp2_ksl_it_begin(const ngtcp2_ksl_it *it) { + return it->i == 0 && it->blk->prev == NULL; +} + +int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + const ngtcp2_range *a = lhs, *b = rhs; + return a->begin < b->begin; +} + +int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs) { + const ngtcp2_range *a = lhs, *b = rhs; + return a->begin < b->begin && + !(ngtcp2_max(a->begin, b->begin) < ngtcp2_min(a->end, b->end)); +} diff --git a/lib/ngtcp2_ksl.h b/lib/ngtcp2_ksl.h new file mode 100644 index 0000000..312a151 --- /dev/null +++ b/lib/ngtcp2_ksl.h @@ -0,0 +1,345 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_KSL_H +#define NGTCP2_KSL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdlib.h> + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_objalloc.h" + +/* + * Skip List using single key instead of range. + */ + +#define NGTCP2_KSL_DEGR 16 +/* NGTCP2_KSL_MAX_NBLK is the maximum number of nodes which a single + block can contain. */ +#define NGTCP2_KSL_MAX_NBLK (2 * NGTCP2_KSL_DEGR - 1) +/* NGTCP2_KSL_MIN_NBLK is the minimum number of nodes which a single + block other than root must contains. */ +#define NGTCP2_KSL_MIN_NBLK (NGTCP2_KSL_DEGR - 1) + +/* + * ngtcp2_ksl_key represents key in ngtcp2_ksl. + */ +typedef void ngtcp2_ksl_key; + +typedef struct ngtcp2_ksl_node ngtcp2_ksl_node; + +typedef struct ngtcp2_ksl_blk ngtcp2_ksl_blk; + +/* + * ngtcp2_ksl_node is a node which contains either ngtcp2_ksl_blk or + * opaque data. If a node is an internal node, it contains + * ngtcp2_ksl_blk. Otherwise, it has data. The key is stored at the + * location starting at key. + */ +struct ngtcp2_ksl_node { + union { + ngtcp2_ksl_blk *blk; + void *data; + }; + union { + uint64_t align; + /* key is a buffer to include key associated to this node. + Because the length of key is unknown until ngtcp2_ksl_init is + called, the actual buffer will be allocated after this + field. */ + uint8_t key[1]; + }; +}; + +/* + * ngtcp2_ksl_blk contains ngtcp2_ksl_node objects. + */ +struct ngtcp2_ksl_blk { + union { + struct { + /* next points to the next block if leaf field is nonzero. */ + ngtcp2_ksl_blk *next; + /* prev points to the previous block if leaf field is nonzero. */ + ngtcp2_ksl_blk *prev; + /* n is the number of nodes this object contains in nodes. */ + uint32_t n; + /* leaf is nonzero if this block contains leaf nodes. */ + uint32_t leaf; + union { + uint64_t align; + /* nodes is a buffer to contain NGTCP2_KSL_MAX_NBLK + ngtcp2_ksl_node objects. Because ngtcp2_ksl_node object is + allocated along with the additional variable length key + storage, the size of buffer is unknown until ngtcp2_ksl_init is + called. */ + uint8_t nodes[1]; + }; + }; + + ngtcp2_opl_entry oplent; + }; +}; + +ngtcp2_objalloc_def(ksl_blk, ngtcp2_ksl_blk, oplent); + +/* + * ngtcp2_ksl_compar is a function type which returns nonzero if key + * |lhs| should be placed before |rhs|. It returns 0 otherwise. + */ +typedef int (*ngtcp2_ksl_compar)(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs); + +typedef struct ngtcp2_ksl ngtcp2_ksl; + +typedef struct ngtcp2_ksl_it ngtcp2_ksl_it; + +/* + * ngtcp2_ksl_it is a forward iterator to iterate nodes. + */ +struct ngtcp2_ksl_it { + const ngtcp2_ksl *ksl; + ngtcp2_ksl_blk *blk; + size_t i; +}; + +/* + * ngtcp2_ksl is a deterministic paged skip list. + */ +struct ngtcp2_ksl { + ngtcp2_objalloc blkalloc; + /* head points to the root block. */ + ngtcp2_ksl_blk *head; + /* front points to the first leaf block. */ + ngtcp2_ksl_blk *front; + /* back points to the last leaf block. */ + ngtcp2_ksl_blk *back; + ngtcp2_ksl_compar compar; + size_t n; + /* keylen is the size of key */ + size_t keylen; + /* nodelen is the actual size of ngtcp2_ksl_node including key + storage. */ + size_t nodelen; +}; + +/* + * ngtcp2_ksl_init initializes |ksl|. |compar| specifies compare + * function. |keylen| is the length of key. + */ +void ngtcp2_ksl_init(ngtcp2_ksl *ksl, ngtcp2_ksl_compar compar, size_t keylen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_ksl_free frees resources allocated for |ksl|. If |ksl| is + * NULL, this function does nothing. It does not free the memory + * region pointed by |ksl| itself. + */ +void ngtcp2_ksl_free(ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_insert inserts |key| with its associated |data|. On + * successful insertion, the iterator points to the inserted node is + * stored in |*it|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + * NGTCP2_ERR_INVALID_ARGUMENT + * |key| already exists. + */ +int ngtcp2_ksl_insert(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key, void *data); + +/* + * ngtcp2_ksl_remove removes the |key| from |ksl|. + * + * This function assigns the iterator to |*it|, which points to the + * node which is located at the right next of the removed node if |it| + * is not NULL. If |key| is not found, no deletion takes place and + * the return value of ngtcp2_ksl_end(ksl) is assigned to |*it|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * |key| does not exist. + */ +int ngtcp2_ksl_remove(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_key *key); + +/* + * ngtcp2_ksl_remove_hint removes the |key| from |ksl|. |hint| must + * point to the same node denoted by |key|. |hint| is used to remove + * a node efficiently in some cases. Other than that, it behaves + * exactly like ngtcp2_ksl_remove. |it| and |hint| can point to the + * same object. + */ +int ngtcp2_ksl_remove_hint(ngtcp2_ksl *ksl, ngtcp2_ksl_it *it, + const ngtcp2_ksl_it *hint, + const ngtcp2_ksl_key *key); + +/* + * ngtcp2_ksl_lower_bound returns the iterator which points to the + * first node which has the key which is equal to |key| or the last + * node which satisfies !compar(&node->key, key). If there is no such + * node, it returns the iterator which satisfies ngtcp2_ksl_it_end(it) + * != 0. + */ +ngtcp2_ksl_it ngtcp2_ksl_lower_bound(ngtcp2_ksl *ksl, + const ngtcp2_ksl_key *key); + +/* + * ngtcp2_ksl_lower_bound_compar works like ngtcp2_ksl_lower_bound, + * but it takes custom function |compar| to do lower bound search. + */ +ngtcp2_ksl_it ngtcp2_ksl_lower_bound_compar(ngtcp2_ksl *ksl, + const ngtcp2_ksl_key *key, + ngtcp2_ksl_compar compar); + +/* + * ngtcp2_ksl_update_key replaces the key of nodes which has |old_key| + * with |new_key|. |new_key| must be strictly greater than the + * previous node and strictly smaller than the next node. + */ +void ngtcp2_ksl_update_key(ngtcp2_ksl *ksl, const ngtcp2_ksl_key *old_key, + const ngtcp2_ksl_key *new_key); + +/* + * ngtcp2_ksl_begin returns the iterator which points to the first + * node. If there is no node in |ksl|, it returns the iterator which + * satisfies ngtcp2_ksl_it_end(it) != 0. + */ +ngtcp2_ksl_it ngtcp2_ksl_begin(const ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_end returns the iterator which points to the node + * following the last node. The returned object satisfies + * ngtcp2_ksl_it_end(). If there is no node in |ksl|, it returns the + * iterator which satisfies ngtcp2_ksl_it_begin(it) != 0. + */ +ngtcp2_ksl_it ngtcp2_ksl_end(const ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_len returns the number of elements stored in |ksl|. + */ +size_t ngtcp2_ksl_len(ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_clear removes all elements stored in |ksl|. + */ +void ngtcp2_ksl_clear(ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_nth_node returns the |n|th node under |blk|. + */ +#define ngtcp2_ksl_nth_node(KSL, BLK, N) \ + ((ngtcp2_ksl_node *)(void *)((BLK)->nodes + (KSL)->nodelen * (N))) + +/* + * ngtcp2_ksl_print prints its internal state in stderr. It assumes + * that the key is of type int64_t. This function should be used for + * the debugging purpose only. + */ +void ngtcp2_ksl_print(ngtcp2_ksl *ksl); + +/* + * ngtcp2_ksl_it_init initializes |it|. + */ +void ngtcp2_ksl_it_init(ngtcp2_ksl_it *it, const ngtcp2_ksl *ksl, + ngtcp2_ksl_blk *blk, size_t i); + +/* + * ngtcp2_ksl_it_get returns the data associated to the node which + * |it| points to. It is undefined to call this function when + * ngtcp2_ksl_it_end(it) returns nonzero. + */ +#define ngtcp2_ksl_it_get(IT) \ + ngtcp2_ksl_nth_node((IT)->ksl, (IT)->blk, (IT)->i)->data + +/* + * ngtcp2_ksl_it_next advances the iterator by one. It is undefined + * if this function is called when ngtcp2_ksl_it_end(it) returns + * nonzero. + */ +#define ngtcp2_ksl_it_next(IT) \ + (++(IT)->i == (IT)->blk->n && (IT)->blk->next \ + ? ((IT)->blk = (IT)->blk->next, (IT)->i = 0) \ + : 0) + +/* + * ngtcp2_ksl_it_prev moves backward the iterator by one. It is + * undefined if this function is called when ngtcp2_ksl_it_begin(it) + * returns nonzero. + */ +void ngtcp2_ksl_it_prev(ngtcp2_ksl_it *it); + +/* + * ngtcp2_ksl_it_end returns nonzero if |it| points to the beyond the + * last node. + */ +#define ngtcp2_ksl_it_end(IT) \ + ((IT)->blk->n == (IT)->i && (IT)->blk->next == NULL) + +/* + * ngtcp2_ksl_it_begin returns nonzero if |it| points to the first + * node. |it| might satisfy both ngtcp2_ksl_it_begin(&it) and + * ngtcp2_ksl_it_end(&it) if the skip list has no node. + */ +int ngtcp2_ksl_it_begin(const ngtcp2_ksl_it *it); + +/* + * ngtcp2_ksl_key returns the key of the node which |it| points to. + * It is undefined to call this function when ngtcp2_ksl_it_end(it) + * returns nonzero. + */ +#define ngtcp2_ksl_it_key(IT) \ + ((ngtcp2_ksl_key *)ngtcp2_ksl_nth_node((IT)->ksl, (IT)->blk, (IT)->i)->key) + +/* + * ngtcp2_ksl_range_compar is an implementation of ngtcp2_ksl_compar. + * lhs->ptr and rhs->ptr must point to ngtcp2_range object and the + * function returns nonzero if (const ngtcp2_range *)(lhs->ptr)->begin + * < (const ngtcp2_range *)(rhs->ptr)->begin. + */ +int ngtcp2_ksl_range_compar(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs); + +/* + * ngtcp2_ksl_range_exclusive_compar is an implementation of + * ngtcp2_ksl_compar. lhs->ptr and rhs->ptr must point to + * ngtcp2_range object and the function returns nonzero if (const + * ngtcp2_range *)(lhs->ptr)->begin < (const ngtcp2_range + * *)(rhs->ptr)->begin and the 2 ranges do not intersect. + */ +int ngtcp2_ksl_range_exclusive_compar(const ngtcp2_ksl_key *lhs, + const ngtcp2_ksl_key *rhs); + +#endif /* NGTCP2_KSL_H */ diff --git a/lib/ngtcp2_log.c b/lib/ngtcp2_log.c new file mode 100644 index 0000000..28f3c93 --- /dev/null +++ b/lib/ngtcp2_log.c @@ -0,0 +1,821 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_log.h" + +#include <stdio.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif +#include <assert.h> +#include <string.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_unreachable.h" +#include "ngtcp2_net.h" + +void ngtcp2_log_init(ngtcp2_log *log, const ngtcp2_cid *scid, + ngtcp2_printf log_printf, ngtcp2_tstamp ts, + void *user_data) { + if (scid) { + ngtcp2_encode_hex(log->scid, scid->data, scid->datalen); + } else { + log->scid[0] = '\0'; + } + log->log_printf = log_printf; + log->ts = log->last_ts = ts; + log->user_data = user_data; +} + +/* + * # Log header + * + * <LEVEL><TIMESTAMP> <SCID> <EVENT> + * + * <LEVEL>: + * Log level. I=Info, W=Warning, E=Error + * + * <TIMESTAMP>: + * Timestamp relative to ngtcp2_log.ts field in milliseconds + * resolution. + * + * <SCID>: + * Source Connection ID in hex string. + * + * <EVENT>: + * Event. pkt=packet, frm=frame, rcv=recovery, cry=crypto, + * con=connection(catch all) + * + * # Frame event + * + * <DIR> <PKN> <PKTNAME> <FRAMENAME>(<FRAMETYPE>) + * + * <DIR>: + * Flow direction. tx=transmission, rx=reception + * + * <PKN>: + * Packet number. + * + * <PKTNAME>: + * Packet name. (e.g., Initial, Handshake, 1RTT) + * + * <FRAMENAME>: + * Frame name. (e.g., STREAM, ACK, PING) + * + * <FRAMETYPE>: + * Frame type in hex string. + */ + +#define NGTCP2_LOG_BUFLEN 4096 + +/* TODO Split second and remaining fraction with comma */ +#define NGTCP2_LOG_HD "I%08" PRIu64 " 0x%s %s" +#define NGTCP2_LOG_PKT NGTCP2_LOG_HD " %s %" PRId64 " %s" +#define NGTCP2_LOG_TP NGTCP2_LOG_HD " remote transport_parameters" + +#define NGTCP2_LOG_FRM_HD_FIELDS(DIR) \ + timestamp_cast(log->last_ts - log->ts), (const char *)log->scid, "frm", \ + (DIR), hd->pkt_num, strpkttype(hd) + +#define NGTCP2_LOG_PKT_HD_FIELDS(DIR) \ + timestamp_cast(log->last_ts - log->ts), (const char *)log->scid, "pkt", \ + (DIR), hd->pkt_num, strpkttype(hd) + +#define NGTCP2_LOG_TP_HD_FIELDS \ + timestamp_cast(log->last_ts - log->ts), (const char *)log->scid, "cry" + +static const char *strerrorcode(uint64_t error_code) { + switch (error_code) { + case NGTCP2_NO_ERROR: + return "NO_ERROR"; + case NGTCP2_INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case NGTCP2_CONNECTION_REFUSED: + return "CONNECTION_REFUSED"; + case NGTCP2_FLOW_CONTROL_ERROR: + return "FLOW_CONTROL_ERROR"; + case NGTCP2_STREAM_LIMIT_ERROR: + return "STREAM_LIMIT_ERROR"; + case NGTCP2_STREAM_STATE_ERROR: + return "STREAM_STATE_ERROR"; + case NGTCP2_FINAL_SIZE_ERROR: + return "FINAL_SIZE_ERROR"; + case NGTCP2_FRAME_ENCODING_ERROR: + return "FRAME_ENCODING_ERROR"; + case NGTCP2_TRANSPORT_PARAMETER_ERROR: + return "TRANSPORT_PARAMETER_ERROR"; + case NGTCP2_CONNECTION_ID_LIMIT_ERROR: + return "CONNECTION_ID_LIMIT_ERROR"; + case NGTCP2_PROTOCOL_VIOLATION: + return "PROTOCOL_VIOLATION"; + case NGTCP2_INVALID_TOKEN: + return "INVALID_TOKEN"; + case NGTCP2_APPLICATION_ERROR: + return "APPLICATION_ERROR"; + case NGTCP2_CRYPTO_BUFFER_EXCEEDED: + return "CRYPTO_BUFFER_EXCEEDED"; + case NGTCP2_KEY_UPDATE_ERROR: + return "KEY_UPDATE_ERROR"; + case NGTCP2_VERSION_NEGOTIATION_ERROR_DRAFT: + return "VERSION_NEGOTIATION_ERROR"; + default: + if (0x100u <= error_code && error_code <= 0x1ffu) { + return "CRYPTO_ERROR"; + } + return "(unknown)"; + } +} + +static const char *strapperrorcode(uint64_t app_error_code) { + (void)app_error_code; + return "(unknown)"; +} + +static const char *strpkttype_long(uint8_t type) { + switch (type) { + case NGTCP2_PKT_INITIAL: + return "Initial"; + case NGTCP2_PKT_RETRY: + return "Retry"; + case NGTCP2_PKT_HANDSHAKE: + return "Handshake"; + case NGTCP2_PKT_0RTT: + return "0RTT"; + default: + return "(unknown)"; + } +} + +static const char *strpkttype(const ngtcp2_pkt_hd *hd) { + if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { + return strpkttype_long(hd->type); + } + + switch (hd->type) { + case NGTCP2_PKT_VERSION_NEGOTIATION: + return "VN"; + case NGTCP2_PKT_STATELESS_RESET: + return "SR"; + case NGTCP2_PKT_1RTT: + return "1RTT"; + default: + return "(unknown)"; + } +} + +static const char *strpkttype_type_flags(uint8_t type, uint8_t flags) { + ngtcp2_pkt_hd hd = {0}; + + hd.type = type; + hd.flags = flags; + + return strpkttype(&hd); +} + +static const char *strevent(ngtcp2_log_event ev) { + switch (ev) { + case NGTCP2_LOG_EVENT_CON: + return "con"; + case NGTCP2_LOG_EVENT_PKT: + return "pkt"; + case NGTCP2_LOG_EVENT_FRM: + return "frm"; + case NGTCP2_LOG_EVENT_RCV: + return "rcv"; + case NGTCP2_LOG_EVENT_CRY: + return "cry"; + case NGTCP2_LOG_EVENT_PTV: + return "ptv"; + case NGTCP2_LOG_EVENT_NONE: + default: + return "non"; + } +} + +static uint64_t timestamp_cast(uint64_t ns) { return ns / NGTCP2_MILLISECONDS; } + +static void log_fr_stream(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_stream *fr, const char *dir) { + log->log_printf( + log->user_data, + (NGTCP2_LOG_PKT " STREAM(0x%02x) id=0x%" PRIx64 " fin=%d offset=%" PRIu64 + " len=%" PRIu64 " uni=%d"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type | fr->flags, fr->stream_id, + fr->fin, fr->offset, ngtcp2_vec_len(fr->data, fr->datacnt), + (fr->stream_id & 0x2) != 0); +} + +static void log_fr_ack(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_ack *fr, const char *dir) { + int64_t largest_ack, min_ack; + size_t i; + + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " ACK(0x%02x) largest_ack=%" PRId64 + " ack_delay=%" PRIu64 "(%" PRIu64 + ") ack_range_count=%zu"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->largest_ack, + fr->ack_delay_unscaled / NGTCP2_MILLISECONDS, fr->ack_delay, + fr->rangecnt); + + largest_ack = fr->largest_ack; + min_ack = fr->largest_ack - (int64_t)fr->first_ack_range; + + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " ACK(0x%02x) range=[%" PRId64 "..%" PRId64 + "] len=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, largest_ack, min_ack, + fr->first_ack_range); + + for (i = 0; i < fr->rangecnt; ++i) { + const ngtcp2_ack_range *range = &fr->ranges[i]; + largest_ack = min_ack - (int64_t)range->gap - 2; + min_ack = largest_ack - (int64_t)range->len; + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " ACK(0x%02x) range=[%" PRId64 "..%" PRId64 + "] gap=%" PRIu64 " len=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, largest_ack, + min_ack, range->gap, range->len); + } + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " ACK(0x%02x) ect0=%" PRIu64 + " ect1=%" PRIu64 " ce=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->ecn.ect0, + fr->ecn.ect1, fr->ecn.ce); + } +} + +static void log_fr_padding(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_padding *fr, const char *dir) { + log->log_printf(log->user_data, (NGTCP2_LOG_PKT " PADDING(0x%02x) len=%zu"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->len); +} + +static void log_fr_reset_stream(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_reset_stream *fr, + const char *dir) { + log->log_printf( + log->user_data, + (NGTCP2_LOG_PKT " RESET_STREAM(0x%02x) id=0x%" PRIx64 + " app_error_code=%s(0x%" PRIx64 ") final_size=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id, + strapperrorcode(fr->app_error_code), fr->app_error_code, fr->final_size); +} + +static void log_fr_connection_close(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_connection_close *fr, + const char *dir) { + char reason[256]; + size_t reasonlen = ngtcp2_min(sizeof(reason) - 1, fr->reasonlen); + + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT + " CONNECTION_CLOSE(0x%02x) error_code=%s(0x%" PRIx64 ") " + "frame_type=%" PRIx64 " reason_len=%zu reason=[%s]"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, + fr->type == NGTCP2_FRAME_CONNECTION_CLOSE + ? strerrorcode(fr->error_code) + : strapperrorcode(fr->error_code), + fr->error_code, fr->frame_type, fr->reasonlen, + ngtcp2_encode_printable_ascii(reason, fr->reason, reasonlen)); +} + +static void log_fr_max_data(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_max_data *fr, const char *dir) { + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " MAX_DATA(0x%02x) max_data=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->max_data); +} + +static void log_fr_max_stream_data(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_max_stream_data *fr, + const char *dir) { + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " MAX_STREAM_DATA(0x%02x) id=0x%" PRIx64 + " max_stream_data=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id, + fr->max_stream_data); +} + +static void log_fr_max_streams(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_max_streams *fr, const char *dir) { + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " MAX_STREAMS(0x%02x) max_streams=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->max_streams); +} + +static void log_fr_ping(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_ping *fr, const char *dir) { + log->log_printf(log->user_data, (NGTCP2_LOG_PKT " PING(0x%02x)"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type); +} + +static void log_fr_data_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_data_blocked *fr, + const char *dir) { + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " DATA_BLOCKED(0x%02x) offset=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->offset); +} + +static void log_fr_stream_data_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_stream_data_blocked *fr, + const char *dir) { + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " STREAM_DATA_BLOCKED(0x%02x) id=0x%" PRIx64 + " offset=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id, + fr->offset); +} + +static void log_fr_streams_blocked(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_streams_blocked *fr, + const char *dir) { + log->log_printf( + log->user_data, + (NGTCP2_LOG_PKT " STREAMS_BLOCKED(0x%02x) max_streams=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->max_streams); +} + +static void log_fr_new_connection_id(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_new_connection_id *fr, + const char *dir) { + uint8_t buf[sizeof(fr->stateless_reset_token) * 2 + 1]; + uint8_t cid[sizeof(fr->cid.data) * 2 + 1]; + + log->log_printf( + log->user_data, + (NGTCP2_LOG_PKT " NEW_CONNECTION_ID(0x%02x) seq=%" PRIu64 + " cid=0x%s retire_prior_to=%" PRIu64 + " stateless_reset_token=0x%s"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->seq, + (const char *)ngtcp2_encode_hex(cid, fr->cid.data, fr->cid.datalen), + fr->retire_prior_to, + (const char *)ngtcp2_encode_hex(buf, fr->stateless_reset_token, + sizeof(fr->stateless_reset_token))); +} + +static void log_fr_stop_sending(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_stop_sending *fr, + const char *dir) { + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " STOP_SENDING(0x%02x) id=0x%" PRIx64 + " app_error_code=%s(0x%" PRIx64 ")"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->stream_id, + strapperrorcode(fr->app_error_code), fr->app_error_code); +} + +static void log_fr_path_challenge(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_path_challenge *fr, + const char *dir) { + uint8_t buf[sizeof(fr->data) * 2 + 1]; + + log->log_printf( + log->user_data, (NGTCP2_LOG_PKT " PATH_CHALLENGE(0x%02x) data=0x%s"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, + (const char *)ngtcp2_encode_hex(buf, fr->data, sizeof(fr->data))); +} + +static void log_fr_path_response(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_path_response *fr, + const char *dir) { + uint8_t buf[sizeof(fr->data) * 2 + 1]; + + log->log_printf( + log->user_data, (NGTCP2_LOG_PKT " PATH_RESPONSE(0x%02x) data=0x%s"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, + (const char *)ngtcp2_encode_hex(buf, fr->data, sizeof(fr->data))); +} + +static void log_fr_crypto(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_crypto *fr, const char *dir) { + log->log_printf( + log->user_data, + (NGTCP2_LOG_PKT " CRYPTO(0x%02x) offset=%" PRIu64 " len=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->offset, + ngtcp2_vec_len(fr->data, fr->datacnt)); +} + +static void log_fr_new_token(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_new_token *fr, const char *dir) { + /* Show at most first 64 bytes of token. If token is longer than 64 + bytes, log first 64 bytes and then append "*" */ + uint8_t buf[128 + 1 + 1]; + uint8_t *p; + + if (fr->token.len > 64) { + p = ngtcp2_encode_hex(buf, fr->token.base, 64); + p[128] = '*'; + p[129] = '\0'; + } else { + p = ngtcp2_encode_hex(buf, fr->token.base, fr->token.len); + } + log->log_printf( + log->user_data, (NGTCP2_LOG_PKT " NEW_TOKEN(0x%02x) token=0x%s len=%zu"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, (const char *)p, fr->token.len); +} + +static void log_fr_retire_connection_id(ngtcp2_log *log, + const ngtcp2_pkt_hd *hd, + const ngtcp2_retire_connection_id *fr, + const char *dir) { + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " RETIRE_CONNECTION_ID(0x%02x) seq=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, fr->seq); +} + +static void log_fr_handshake_done(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_handshake_done *fr, + const char *dir) { + log->log_printf(log->user_data, (NGTCP2_LOG_PKT " HANDSHAKE_DONE(0x%02x)"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type); +} + +static void log_fr_datagram(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_datagram *fr, const char *dir) { + log->log_printf(log->user_data, + (NGTCP2_LOG_PKT " DATAGRAM(0x%02x) len=%" PRIu64), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, + ngtcp2_vec_len(fr->data, fr->datacnt)); +} + +static void log_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr, const char *dir) { + switch (fr->type) { + case NGTCP2_FRAME_STREAM: + log_fr_stream(log, hd, &fr->stream, dir); + break; + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + log_fr_ack(log, hd, &fr->ack, dir); + break; + case NGTCP2_FRAME_PADDING: + log_fr_padding(log, hd, &fr->padding, dir); + break; + case NGTCP2_FRAME_RESET_STREAM: + log_fr_reset_stream(log, hd, &fr->reset_stream, dir); + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + log_fr_connection_close(log, hd, &fr->connection_close, dir); + break; + case NGTCP2_FRAME_MAX_DATA: + log_fr_max_data(log, hd, &fr->max_data, dir); + break; + case NGTCP2_FRAME_MAX_STREAM_DATA: + log_fr_max_stream_data(log, hd, &fr->max_stream_data, dir); + break; + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + log_fr_max_streams(log, hd, &fr->max_streams, dir); + break; + case NGTCP2_FRAME_PING: + log_fr_ping(log, hd, &fr->ping, dir); + break; + case NGTCP2_FRAME_DATA_BLOCKED: + log_fr_data_blocked(log, hd, &fr->data_blocked, dir); + break; + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + log_fr_stream_data_blocked(log, hd, &fr->stream_data_blocked, dir); + break; + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + log_fr_streams_blocked(log, hd, &fr->streams_blocked, dir); + break; + case NGTCP2_FRAME_NEW_CONNECTION_ID: + log_fr_new_connection_id(log, hd, &fr->new_connection_id, dir); + break; + case NGTCP2_FRAME_STOP_SENDING: + log_fr_stop_sending(log, hd, &fr->stop_sending, dir); + break; + case NGTCP2_FRAME_PATH_CHALLENGE: + log_fr_path_challenge(log, hd, &fr->path_challenge, dir); + break; + case NGTCP2_FRAME_PATH_RESPONSE: + log_fr_path_response(log, hd, &fr->path_response, dir); + break; + case NGTCP2_FRAME_CRYPTO: + log_fr_crypto(log, hd, &fr->crypto, dir); + break; + case NGTCP2_FRAME_NEW_TOKEN: + log_fr_new_token(log, hd, &fr->new_token, dir); + break; + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + log_fr_retire_connection_id(log, hd, &fr->retire_connection_id, dir); + break; + case NGTCP2_FRAME_HANDSHAKE_DONE: + log_fr_handshake_done(log, hd, &fr->handshake_done, dir); + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + log_fr_datagram(log, hd, &fr->datagram, dir); + break; + default: + ngtcp2_unreachable(); + } +} + +void ngtcp2_log_rx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr) { + if (!log->log_printf) { + return; + } + + log_fr(log, hd, fr, "rx"); +} + +void ngtcp2_log_tx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr) { + if (!log->log_printf) { + return; + } + + log_fr(log, hd, fr, "tx"); +} + +void ngtcp2_log_rx_vn(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv) { + size_t i; + + if (!log->log_printf) { + return; + } + + for (i = 0; i < nsv; ++i) { + log->log_printf(log->user_data, (NGTCP2_LOG_PKT " v=0x%08x"), + NGTCP2_LOG_PKT_HD_FIELDS("rx"), sv[i]); + } +} + +void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset *sr) { + uint8_t buf[sizeof(sr->stateless_reset_token) * 2 + 1]; + ngtcp2_pkt_hd shd; + ngtcp2_pkt_hd *hd = &shd; + + if (!log->log_printf) { + return; + } + + memset(&shd, 0, sizeof(shd)); + + shd.type = NGTCP2_PKT_STATELESS_RESET; + + log->log_printf( + log->user_data, (NGTCP2_LOG_PKT " token=0x%s randlen=%zu"), + NGTCP2_LOG_PKT_HD_FIELDS("rx"), + (const char *)ngtcp2_encode_hex(buf, sr->stateless_reset_token, + sizeof(sr->stateless_reset_token)), + sr->randlen); +} + +void ngtcp2_log_remote_tp(ngtcp2_log *log, uint8_t exttype, + const ngtcp2_transport_params *params) { + uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN * 2 + 1]; + uint8_t addr[16 * 2 + 7 + 1]; + uint8_t cid[NGTCP2_MAX_CIDLEN * 2 + 1]; + size_t i; + const ngtcp2_sockaddr_in *sa_in; + const ngtcp2_sockaddr_in6 *sa_in6; + const uint8_t *p; + uint32_t version; + + if (!log->log_printf) { + return; + } + + if (exttype == NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS) { + if (params->stateless_reset_token_present) { + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " stateless_reset_token=0x%s"), + NGTCP2_LOG_TP_HD_FIELDS, + (const char *)ngtcp2_encode_hex( + token, params->stateless_reset_token, + sizeof(params->stateless_reset_token))); + } + + if (params->preferred_address_present) { + if (params->preferred_address.ipv4_present) { + sa_in = ¶ms->preferred_address.ipv4; + + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " preferred_address.ipv4_addr=%s"), + NGTCP2_LOG_TP_HD_FIELDS, + (const char *)ngtcp2_encode_ipv4( + addr, (const uint8_t *)&sa_in->sin_addr)); + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " preferred_address.ipv4_port=%u"), + NGTCP2_LOG_TP_HD_FIELDS, ngtcp2_ntohs(sa_in->sin_port)); + } + + if (params->preferred_address.ipv6_present) { + sa_in6 = ¶ms->preferred_address.ipv6; + + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " preferred_address.ipv6_addr=%s"), + NGTCP2_LOG_TP_HD_FIELDS, + (const char *)ngtcp2_encode_ipv6( + addr, (const uint8_t *)&sa_in6->sin6_addr)); + log->log_printf( + log->user_data, (NGTCP2_LOG_TP " preferred_address.ipv6_port=%u"), + NGTCP2_LOG_TP_HD_FIELDS, ngtcp2_ntohs(sa_in6->sin6_port)); + } + + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " preferred_address.cid=0x%s"), + NGTCP2_LOG_TP_HD_FIELDS, + (const char *)ngtcp2_encode_hex( + cid, params->preferred_address.cid.data, + params->preferred_address.cid.datalen)); + log->log_printf( + log->user_data, + (NGTCP2_LOG_TP " preferred_address.stateless_reset_token=0x%s"), + NGTCP2_LOG_TP_HD_FIELDS, + (const char *)ngtcp2_encode_hex( + token, params->preferred_address.stateless_reset_token, + sizeof(params->preferred_address.stateless_reset_token))); + } + + log->log_printf( + log->user_data, + (NGTCP2_LOG_TP " original_destination_connection_id=0x%s"), + NGTCP2_LOG_TP_HD_FIELDS, + (const char *)ngtcp2_encode_hex(cid, params->original_dcid.data, + params->original_dcid.datalen)); + + if (params->retry_scid_present) { + log->log_printf( + log->user_data, (NGTCP2_LOG_TP " retry_source_connection_id=0x%s"), + NGTCP2_LOG_TP_HD_FIELDS, + (const char *)ngtcp2_encode_hex(cid, params->retry_scid.data, + params->retry_scid.datalen)); + } + } + + log->log_printf( + log->user_data, (NGTCP2_LOG_TP " initial_source_connection_id=0x%s"), + NGTCP2_LOG_TP_HD_FIELDS, + (const char *)ngtcp2_encode_hex(cid, params->initial_scid.data, + params->initial_scid.datalen)); + + log->log_printf( + log->user_data, + (NGTCP2_LOG_TP " initial_max_stream_data_bidi_local=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_stream_data_bidi_local); + log->log_printf( + log->user_data, + (NGTCP2_LOG_TP " initial_max_stream_data_bidi_remote=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_stream_data_bidi_remote); + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " initial_max_stream_data_uni=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_stream_data_uni); + log->log_printf(log->user_data, (NGTCP2_LOG_TP " initial_max_data=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_data); + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " initial_max_streams_bidi=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_streams_bidi); + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " initial_max_streams_uni=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->initial_max_streams_uni); + log->log_printf(log->user_data, (NGTCP2_LOG_TP " max_idle_timeout=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, + params->max_idle_timeout / NGTCP2_MILLISECONDS); + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " max_udp_payload_size=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->max_udp_payload_size); + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " ack_delay_exponent=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->ack_delay_exponent); + log->log_printf(log->user_data, (NGTCP2_LOG_TP " max_ack_delay=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, + params->max_ack_delay / NGTCP2_MILLISECONDS); + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " active_connection_id_limit=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->active_connection_id_limit); + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " disable_active_migration=%d"), + NGTCP2_LOG_TP_HD_FIELDS, params->disable_active_migration); + log->log_printf(log->user_data, + (NGTCP2_LOG_TP " max_datagram_frame_size=%" PRIu64), + NGTCP2_LOG_TP_HD_FIELDS, params->max_datagram_frame_size); + log->log_printf(log->user_data, (NGTCP2_LOG_TP " grease_quic_bit=%d"), + NGTCP2_LOG_TP_HD_FIELDS, params->grease_quic_bit); + + if (params->version_info_present) { + log->log_printf( + log->user_data, + (NGTCP2_LOG_TP " version_information.chosen_version=0x%08x"), + NGTCP2_LOG_TP_HD_FIELDS, params->version_info.chosen_version); + + assert(!(params->version_info.other_versionslen & 0x3)); + + for (i = 0, p = params->version_info.other_versions; + i < params->version_info.other_versionslen; i += sizeof(uint32_t)) { + p = ngtcp2_get_uint32(&version, p); + + log->log_printf( + log->user_data, + (NGTCP2_LOG_TP " version_information.other_versions[%zu]=0x%08x"), + NGTCP2_LOG_TP_HD_FIELDS, i >> 2, version); + } + } +} + +void ngtcp2_log_pkt_lost(ngtcp2_log *log, int64_t pkt_num, uint8_t type, + uint8_t flags, ngtcp2_tstamp sent_ts) { + if (!log->log_printf) { + return; + } + + ngtcp2_log_info(log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 " lost type=%s sent_ts=%" PRIu64, pkt_num, + strpkttype_type_flags(type, flags), sent_ts); +} + +static void log_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const char *dir) { + uint8_t dcid[sizeof(hd->dcid.data) * 2 + 1]; + uint8_t scid[sizeof(hd->scid.data) * 2 + 1]; + + if (!log->log_printf) { + return; + } + + if (hd->type == NGTCP2_PKT_1RTT) { + ngtcp2_log_info( + log, NGTCP2_LOG_EVENT_PKT, "%s pkn=%" PRId64 " dcid=0x%s type=%s k=%d", + dir, hd->pkt_num, + (const char *)ngtcp2_encode_hex(dcid, hd->dcid.data, hd->dcid.datalen), + strpkttype(hd), (hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE) != 0); + } else { + ngtcp2_log_info( + log, NGTCP2_LOG_EVENT_PKT, + "%s pkn=%" PRId64 " dcid=0x%s scid=0x%s version=0x%08x type=%s len=%zu", + dir, hd->pkt_num, + (const char *)ngtcp2_encode_hex(dcid, hd->dcid.data, hd->dcid.datalen), + (const char *)ngtcp2_encode_hex(scid, hd->scid.data, hd->scid.datalen), + hd->version, strpkttype(hd), hd->len); + } +} + +void ngtcp2_log_rx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) { + log_pkt_hd(log, hd, "rx"); +} + +void ngtcp2_log_tx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) { + log_pkt_hd(log, hd, "tx"); +} + +void ngtcp2_log_info(ngtcp2_log *log, ngtcp2_log_event ev, const char *fmt, + ...) { + va_list ap; + int n; + char buf[NGTCP2_LOG_BUFLEN]; + + if (!log->log_printf) { + return; + } + + va_start(ap, fmt); + n = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + if (n < 0 || (size_t)n >= sizeof(buf)) { + return; + } + + log->log_printf(log->user_data, (NGTCP2_LOG_HD " %s"), + timestamp_cast(log->last_ts - log->ts), log->scid, + strevent(ev), buf); +} + +void ngtcp2_log_tx_cancel(ngtcp2_log *log, const ngtcp2_pkt_hd *hd) { + ngtcp2_log_info(log, NGTCP2_LOG_EVENT_PKT, + "cancel tx pkn=%" PRId64 " type=%s", hd->pkt_num, + strpkttype(hd)); +} diff --git a/lib/ngtcp2_log.h b/lib/ngtcp2_log.h new file mode 100644 index 0000000..029ef1b --- /dev/null +++ b/lib/ngtcp2_log.h @@ -0,0 +1,123 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_LOG_H +#define NGTCP2_LOG_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pkt.h" + +typedef struct ngtcp2_log { + /* log_printf is a sink to write log. NULL means no logging + output. */ + ngtcp2_printf log_printf; + /* ts is the time point used to write time delta in the log. */ + ngtcp2_tstamp ts; + /* last_ts is the most recent time point that this object is + told. */ + ngtcp2_tstamp last_ts; + /* user_data is user-defined opaque data which is passed to + log_pritnf. */ + void *user_data; + /* scid is SCID encoded as NULL-terminated hex string. */ + uint8_t scid[NGTCP2_MAX_CIDLEN * 2 + 1]; +} ngtcp2_log; + +/** + * @enum + * + * :type:`ngtcp2_log_event` defines an event of ngtcp2 library + * internal logger. + */ +typedef enum ngtcp2_log_event { + /** + * :enum:`NGTCP2_LOG_EVENT_NONE` represents no event. + */ + NGTCP2_LOG_EVENT_NONE, + /** + * :enum:`NGTCP2_LOG_EVENT_CON` is a connection (catch-all) event + */ + NGTCP2_LOG_EVENT_CON, + /** + * :enum:`NGTCP2_LOG_EVENT_PKT` is a packet event. + */ + NGTCP2_LOG_EVENT_PKT, + /** + * :enum:`NGTCP2_LOG_EVENT_FRM` is a QUIC frame event. + */ + NGTCP2_LOG_EVENT_FRM, + /** + * :enum:`NGTCP2_LOG_EVENT_RCV` is a congestion and recovery event. + */ + NGTCP2_LOG_EVENT_RCV, + /** + * :enum:`NGTCP2_LOG_EVENT_CRY` is a crypto event. + */ + NGTCP2_LOG_EVENT_CRY, + /** + * :enum:`NGTCP2_LOG_EVENT_PTV` is a path validation event. + */ + NGTCP2_LOG_EVENT_PTV, +} ngtcp2_log_event; + +void ngtcp2_log_init(ngtcp2_log *log, const ngtcp2_cid *scid, + ngtcp2_printf log_printf, ngtcp2_tstamp ts, + void *user_data); + +void ngtcp2_log_rx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr); +void ngtcp2_log_tx_fr(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const ngtcp2_frame *fr); + +void ngtcp2_log_rx_vn(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, + const uint32_t *sv, size_t nsv); + +void ngtcp2_log_rx_sr(ngtcp2_log *log, const ngtcp2_pkt_stateless_reset *sr); + +void ngtcp2_log_remote_tp(ngtcp2_log *log, uint8_t exttype, + const ngtcp2_transport_params *params); + +void ngtcp2_log_pkt_lost(ngtcp2_log *log, int64_t pkt_num, uint8_t type, + uint8_t flags, ngtcp2_tstamp sent_ts); + +void ngtcp2_log_rx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd); + +void ngtcp2_log_tx_pkt_hd(ngtcp2_log *log, const ngtcp2_pkt_hd *hd); + +void ngtcp2_log_tx_cancel(ngtcp2_log *log, const ngtcp2_pkt_hd *hd); + +/** + * @function + * + * `ngtcp2_log_info` writes info level log. + */ +void ngtcp2_log_info(ngtcp2_log *log, ngtcp2_log_event ev, const char *fmt, + ...); + +#endif /* NGTCP2_LOG_H */ diff --git a/lib/ngtcp2_macro.h b/lib/ngtcp2_macro.h new file mode 100644 index 0000000..28d3461 --- /dev/null +++ b/lib/ngtcp2_macro.h @@ -0,0 +1,58 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_MACRO_H +#define NGTCP2_MACRO_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stddef.h> + +#include <ngtcp2/ngtcp2.h> + +#define ngtcp2_min(A, B) ((A) < (B) ? (A) : (B)) +#define ngtcp2_max(A, B) ((A) > (B) ? (A) : (B)) + +#define ngtcp2_struct_of(ptr, type, member) \ + ((type *)(void *)((char *)(ptr)-offsetof(type, member))) + +/* ngtcp2_list_insert inserts |T| before |*PD|. The contract is that + this is singly linked list, and the next element is pointed by next + field of the previous element. |PD| must be a pointer to the + pointer to the next field of the previous element of |*PD|: if C is + the previous element of |PD|, PD = &C->next. */ +#define ngtcp2_list_insert(T, PD) \ + do { \ + (T)->next = *(PD); \ + *(PD) = (T); \ + } while (0) + +/* + * ngtcp2_arraylen returns the number of elements in array |A|. + */ +#define ngtcp2_arraylen(A) (sizeof(A) / sizeof(A[0])) + +#endif /* NGTCP2_MACRO_H */ diff --git a/lib/ngtcp2_map.c b/lib/ngtcp2_map.c new file mode 100644 index 0000000..12bc6e8 --- /dev/null +++ b/lib/ngtcp2_map.c @@ -0,0 +1,336 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_map.h" + +#include <string.h> +#include <assert.h> +#include <stdio.h> + +#include "ngtcp2_conv.h" + +#define NGTCP2_INITIAL_TABLE_LENBITS 4 + +void ngtcp2_map_init(ngtcp2_map *map, const ngtcp2_mem *mem) { + map->mem = mem; + map->tablelen = 0; + map->tablelenbits = 0; + map->table = NULL; + map->size = 0; +} + +void ngtcp2_map_free(ngtcp2_map *map) { + if (!map) { + return; + } + + ngtcp2_mem_free(map->mem, map->table); +} + +void ngtcp2_map_each_free(ngtcp2_map *map, int (*func)(void *data, void *ptr), + void *ptr) { + uint32_t i; + ngtcp2_map_bucket *bkt; + + for (i = 0; i < map->tablelen; ++i) { + bkt = &map->table[i]; + + if (bkt->data == NULL) { + continue; + } + + func(bkt->data, ptr); + } +} + +int ngtcp2_map_each(ngtcp2_map *map, int (*func)(void *data, void *ptr), + void *ptr) { + int rv; + uint32_t i; + ngtcp2_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(ngtcp2_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, + ngtcp2_map_bucket *bkt, size_t idx) { + return (idx - h2idx(bkt->hash, tablelenbits)) & (tablelen - 1); +} + +static void map_bucket_swap(ngtcp2_map_bucket *bkt, uint32_t *phash, + ngtcp2_map_key_type *pkey, void **pdata) { + uint32_t h = bkt->hash; + ngtcp2_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(ngtcp2_map_bucket *bkt, uint32_t hash, + ngtcp2_map_key_type key, void *data) { + bkt->hash = hash; + bkt->key = key; + bkt->data = data; +} + +void ngtcp2_map_print_distance(ngtcp2_map *map) { + uint32_t i; + size_t idx; + ngtcp2_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(ngtcp2_map_bucket *table, uint32_t tablelen, + uint32_t tablelenbits, uint32_t hash, ngtcp2_map_key_type key, + void *data) { + size_t idx = h2idx(hash, tablelenbits); + size_t d = 0, dd; + ngtcp2_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 NGTCP2_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(ngtcp2_map *map, uint32_t new_tablelen, + uint32_t new_tablelenbits) { + uint32_t i; + ngtcp2_map_bucket *new_table; + ngtcp2_map_bucket *bkt; + int rv; + (void)rv; + + new_table = + ngtcp2_mem_calloc(map->mem, new_tablelen, sizeof(ngtcp2_map_bucket)); + if (new_table == NULL) { + return NGTCP2_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); + } + + ngtcp2_mem_free(map->mem, map->table); + map->tablelen = new_tablelen; + map->tablelenbits = new_tablelenbits; + map->table = new_table; + + return 0; +} + +int ngtcp2_map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data) { + int rv; + + assert(data); + + /* Load factor is 0.75 */ + 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 << NGTCP2_INITIAL_TABLE_LENBITS, + NGTCP2_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 *ngtcp2_map_find(ngtcp2_map *map, ngtcp2_map_key_type key) { + uint32_t h; + size_t idx; + ngtcp2_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 ngtcp2_map_remove(ngtcp2_map *map, ngtcp2_map_key_type key) { + uint32_t h; + size_t idx, didx; + ngtcp2_map_bucket *bkt; + size_t d = 0; + + if (map->size == 0) { + return NGTCP2_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 NGTCP2_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 ngtcp2_map_clear(ngtcp2_map *map) { + if (map->tablelen == 0) { + return; + } + + memset(map->table, 0, sizeof(*map->table) * map->tablelen); + map->size = 0; +} + +size_t ngtcp2_map_size(ngtcp2_map *map) { return map->size; } diff --git a/lib/ngtcp2_map.h b/lib/ngtcp2_map.h new file mode 100644 index 0000000..a64344a --- /dev/null +++ b/lib/ngtcp2_map.h @@ -0,0 +1,136 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_MAP_H +#define NGTCP2_MAP_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" + +/* Implementation of unordered map */ + +typedef uint64_t ngtcp2_map_key_type; + +typedef struct ngtcp2_map_bucket { + uint32_t hash; + ngtcp2_map_key_type key; + void *data; +} ngtcp2_map_bucket; + +typedef struct ngtcp2_map { + ngtcp2_map_bucket *table; + const ngtcp2_mem *mem; + size_t size; + uint32_t tablelen; + uint32_t tablelenbits; +} ngtcp2_map; + +/* + * Initializes the map |map|. + */ +void ngtcp2_map_init(ngtcp2_map *map, const ngtcp2_mem *mem); + +/* + * Deallocates any resources allocated for |map|. The stored entries + * are not freed by this function. Use ngtcp2_map_each_free() to free + * each entries. + */ +void ngtcp2_map_free(ngtcp2_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 ngtcp2_map_each_free(ngtcp2_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: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * The item associated by |key| already exists. + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_map_insert(ngtcp2_map *map, ngtcp2_map_key_type key, void *data); + +/* + * Returns the data associated by the key |key|. If there is no such + * data, this function returns NULL. + */ +void *ngtcp2_map_find(ngtcp2_map *map, ngtcp2_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: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * The data associated by |key| does not exist. + */ +int ngtcp2_map_remove(ngtcp2_map *map, ngtcp2_map_key_type key); + +/* + * Removes all entries from |map|. + */ +void ngtcp2_map_clear(ngtcp2_map *map); + +/* + * Returns the number of items stored in the map |map|. + */ +size_t ngtcp2_map_size(ngtcp2_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 + * ngtcp2_map_each_free() instead. + */ +int ngtcp2_map_each(ngtcp2_map *map, int (*func)(void *data, void *ptr), + void *ptr); + +void ngtcp2_map_print_distance(ngtcp2_map *map); + +#endif /* NGTCP2_MAP_H */ diff --git a/lib/ngtcp2_mem.c b/lib/ngtcp2_mem.c new file mode 100644 index 0000000..bcce0b5 --- /dev/null +++ b/lib/ngtcp2_mem.c @@ -0,0 +1,113 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2014 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_mem.h" + +#include <stdio.h> + +static void *default_malloc(size_t size, void *user_data) { + (void)user_data; + + return malloc(size); +} + +static void default_free(void *ptr, void *user_data) { + (void)user_data; + + free(ptr); +} + +static void *default_calloc(size_t nmemb, size_t size, void *user_data) { + (void)user_data; + + return calloc(nmemb, size); +} + +static void *default_realloc(void *ptr, size_t size, void *user_data) { + (void)user_data; + + return realloc(ptr, size); +} + +static const ngtcp2_mem mem_default = {NULL, default_malloc, default_free, + default_calloc, default_realloc}; + +const ngtcp2_mem *ngtcp2_mem_default(void) { return &mem_default; } + +#ifndef MEMDEBUG +void *ngtcp2_mem_malloc(const ngtcp2_mem *mem, size_t size) { + return mem->malloc(size, mem->user_data); +} + +void ngtcp2_mem_free(const ngtcp2_mem *mem, void *ptr) { + mem->free(ptr, mem->user_data); +} + +void *ngtcp2_mem_calloc(const ngtcp2_mem *mem, size_t nmemb, size_t size) { + return mem->calloc(nmemb, size, mem->user_data); +} + +void *ngtcp2_mem_realloc(const ngtcp2_mem *mem, void *ptr, size_t size) { + return mem->realloc(ptr, size, mem->user_data); +} +#else /* MEMDEBUG */ +void *ngtcp2_mem_malloc_debug(const ngtcp2_mem *mem, size_t size, + const char *func, const char *file, size_t line) { + void *nptr = mem->malloc(size, mem->user_data); + + fprintf(stderr, "malloc %p size=%zu in %s at %s:%zu\n", nptr, size, func, + file, line); + + return nptr; +} + +void ngtcp2_mem_free_debug(const ngtcp2_mem *mem, void *ptr, const char *func, + const char *file, size_t line) { + fprintf(stderr, "free ptr=%p in %s at %s:%zu\n", ptr, func, file, line); + + mem->free(ptr, mem->user_data); +} + +void *ngtcp2_mem_calloc_debug(const ngtcp2_mem *mem, size_t nmemb, size_t size, + const char *func, const char *file, size_t line) { + void *nptr = mem->calloc(nmemb, size, mem->user_data); + + fprintf(stderr, "calloc %p nmemb=%zu size=%zu in %s at %s:%zu\n", nptr, nmemb, + size, func, file, line); + + return nptr; +} + +void *ngtcp2_mem_realloc_debug(const ngtcp2_mem *mem, void *ptr, size_t size, + const char *func, const char *file, + size_t line) { + void *nptr = mem->realloc(ptr, size, mem->user_data); + + fprintf(stderr, "realloc %p ptr=%p size=%zu in %s at %s:%zu\n", nptr, ptr, + size, func, file, line); + + return nptr; +} +#endif /* MEMDEBUG */ diff --git a/lib/ngtcp2_mem.h b/lib/ngtcp2_mem.h new file mode 100644 index 0000000..c99b6c5 --- /dev/null +++ b/lib/ngtcp2_mem.h @@ -0,0 +1,72 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2014 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_MEM_H +#define NGTCP2_MEM_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* Convenient wrapper functions to call allocator function in + |mem|. */ +#ifndef MEMDEBUG +void *ngtcp2_mem_malloc(const ngtcp2_mem *mem, size_t size); + +void ngtcp2_mem_free(const ngtcp2_mem *mem, void *ptr); + +void *ngtcp2_mem_calloc(const ngtcp2_mem *mem, size_t nmemb, size_t size); + +void *ngtcp2_mem_realloc(const ngtcp2_mem *mem, void *ptr, size_t size); +#else /* MEMDEBUG */ +void *ngtcp2_mem_malloc_debug(const ngtcp2_mem *mem, size_t size, + const char *func, const char *file, size_t line); + +# define ngtcp2_mem_malloc(MEM, SIZE) \ + ngtcp2_mem_malloc_debug((MEM), (SIZE), __func__, __FILE__, __LINE__) + +void ngtcp2_mem_free_debug(const ngtcp2_mem *mem, void *ptr, const char *func, + const char *file, size_t line); + +# define ngtcp2_mem_free(MEM, PTR) \ + ngtcp2_mem_free_debug((MEM), (PTR), __func__, __FILE__, __LINE__) + +void *ngtcp2_mem_calloc_debug(const ngtcp2_mem *mem, size_t nmemb, size_t size, + const char *func, const char *file, size_t line); + +# define ngtcp2_mem_calloc(MEM, NMEMB, SIZE) \ + ngtcp2_mem_calloc_debug((MEM), (NMEMB), (SIZE), __func__, __FILE__, \ + __LINE__) + +void *ngtcp2_mem_realloc_debug(const ngtcp2_mem *mem, void *ptr, size_t size, + const char *func, const char *file, size_t line); + +# define ngtcp2_mem_realloc(MEM, PTR, SIZE) \ + ngtcp2_mem_realloc_debug((MEM), (PTR), (SIZE), __func__, __FILE__, __LINE__) +#endif /* MEMDEBUG */ + +#endif /* NGTCP2_MEM_H */ diff --git a/lib/ngtcp2_net.h b/lib/ngtcp2_net.h new file mode 100644 index 0000000..cd73d2b --- /dev/null +++ b/lib/ngtcp2_net.h @@ -0,0 +1,136 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_NET_H +#define NGTCP2_NET_H + +/* This header file is explicitly allowed to be shared with + ngtcp2_crypto library. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* 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 <ngtcp2/ngtcp2.h> + +#if defined(HAVE_BSWAP_64) || \ + (defined(HAVE_DECL_BSWAP_64) && HAVE_DECL_BSWAP_64 > 0) +# define ngtcp2_bswap64 bswap_64 +#else /* !HAVE_BSWAP_64 */ +# define ngtcp2_bswap64(N) \ + ((uint64_t)(ngtcp2_ntohl((uint32_t)(N))) << 32 | \ + ngtcp2_ntohl((uint32_t)((N) >> 32))) +#endif /* !HAVE_BSWAP_64 */ + +#if defined(HAVE_BE64TOH) || \ + (defined(HAVE_DECL_BE64TOH) && HAVE_DECL_BE64TOH > 0) +# define ngtcp2_ntohl64(N) be64toh(N) +# define ngtcp2_htonl64(N) htobe64(N) +#else /* !HAVE_BE64TOH */ +# if defined(WORDS_BIGENDIAN) +# define ngtcp2_ntohl64(N) (N) +# define ngtcp2_htonl64(N) (N) +# else /* !WORDS_BIGENDIAN */ +# define ngtcp2_ntohl64(N) ngtcp2_bswap64(N) +# define ngtcp2_htonl64(N) ngtcp2_bswap64(N) +# endif /* !WORDS_BIGENDIAN */ +#endif /* !HAVE_BE64TOH */ + +#if defined(WIN32) +/* Windows requires ws2_32 library for ntonl family functions. We + define inline functions for those function so that we don't have + dependeny on that lib. */ + +# ifdef _MSC_VER +# define STIN static __inline +# else +# define STIN static inline +# endif + +STIN uint32_t ngtcp2_htonl(uint32_t hostlong) { + uint32_t res; + unsigned char *p = (unsigned char *)&res; + *p++ = (unsigned char)(hostlong >> 24); + *p++ = (hostlong >> 16) & 0xffu; + *p++ = (hostlong >> 8) & 0xffu; + *p = hostlong & 0xffu; + return res; +} + +STIN uint16_t ngtcp2_htons(uint16_t hostshort) { + uint16_t res; + unsigned char *p = (unsigned char *)&res; + *p++ = (unsigned char)(hostshort >> 8); + *p = hostshort & 0xffu; + return res; +} + +STIN uint32_t ngtcp2_ntohl(uint32_t netlong) { + uint32_t res; + unsigned char *p = (unsigned char *)&netlong; + res = (uint32_t)(*p++ << 24); + res += (uint32_t)(*p++ << 16); + res += (uint32_t)(*p++ << 8); + res += *p; + return res; +} + +STIN uint16_t ngtcp2_ntohs(uint16_t netshort) { + uint16_t res; + unsigned char *p = (unsigned char *)&netshort; + res = (uint16_t)(*p++ << 8); + res += *p; + return res; +} + +#else /* !WIN32 */ + +# define ngtcp2_htonl htonl +# define ngtcp2_htons htons +# define ngtcp2_ntohl ntohl +# define ngtcp2_ntohs ntohs + +#endif /* !WIN32 */ + +#endif /* NGTCP2_NET_H */ diff --git a/lib/ngtcp2_objalloc.c b/lib/ngtcp2_objalloc.c new file mode 100644 index 0000000..8b06cdd --- /dev/null +++ b/lib/ngtcp2_objalloc.c @@ -0,0 +1,40 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_objalloc.h" + +void ngtcp2_objalloc_init(ngtcp2_objalloc *objalloc, size_t blklen, + const ngtcp2_mem *mem) { + ngtcp2_balloc_init(&objalloc->balloc, blklen, mem); + ngtcp2_opl_init(&objalloc->opl); +} + +void ngtcp2_objalloc_free(ngtcp2_objalloc *objalloc) { + ngtcp2_balloc_free(&objalloc->balloc); +} + +void ngtcp2_objalloc_clear(ngtcp2_objalloc *objalloc) { + ngtcp2_opl_clear(&objalloc->opl); + ngtcp2_balloc_clear(&objalloc->balloc); +} diff --git a/lib/ngtcp2_objalloc.h b/lib/ngtcp2_objalloc.h new file mode 100644 index 0000000..f1bbd3a --- /dev/null +++ b/lib/ngtcp2_objalloc.h @@ -0,0 +1,140 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_OBJALLOC_H +#define NGTCP2_OBJALLOC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_balloc.h" +#include "ngtcp2_opl.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_mem.h" + +/* + * ngtcp2_objalloc combines ngtcp2_balloc and ngtcp2_opl, and provides + * an object pool with the custom allocator to reduce the allocation + * and deallocation overheads for small objects. + */ +typedef struct ngtcp2_objalloc { + ngtcp2_balloc balloc; + ngtcp2_opl opl; +} ngtcp2_objalloc; + +/* + * ngtcp2_objalloc_init initializes |objalloc|. |blklen| is directly + * passed to ngtcp2_balloc_init. + */ +void ngtcp2_objalloc_init(ngtcp2_objalloc *objalloc, size_t blklen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_objalloc_free releases all allocated resources. + */ +void ngtcp2_objalloc_free(ngtcp2_objalloc *objalloc); + +/* + * ngtcp2_objalloc_clear releases all allocated resources and + * initializes its state. + */ +void ngtcp2_objalloc_clear(ngtcp2_objalloc *objalloc); + +#ifndef NOMEMPOOL +# define ngtcp2_objalloc_def(NAME, TYPE, OPLENTFIELD) \ + inline static void ngtcp2_objalloc_##NAME##_init( \ + ngtcp2_objalloc *objalloc, size_t nmemb, const ngtcp2_mem *mem) { \ + ngtcp2_objalloc_init( \ + objalloc, ((sizeof(TYPE) + 0xfu) & ~(uintptr_t)0xfu) * nmemb, mem); \ + } \ + \ + inline static TYPE *ngtcp2_objalloc_##NAME##_get( \ + ngtcp2_objalloc *objalloc) { \ + ngtcp2_opl_entry *oplent = ngtcp2_opl_pop(&objalloc->opl); \ + TYPE *obj; \ + int rv; \ + \ + if (!oplent) { \ + rv = \ + ngtcp2_balloc_get(&objalloc->balloc, (void **)&obj, sizeof(TYPE)); \ + if (rv != 0) { \ + return NULL; \ + } \ + \ + return obj; \ + } \ + \ + return ngtcp2_struct_of(oplent, TYPE, OPLENTFIELD); \ + } \ + \ + inline static TYPE *ngtcp2_objalloc_##NAME##_len_get( \ + ngtcp2_objalloc *objalloc, size_t len) { \ + ngtcp2_opl_entry *oplent = ngtcp2_opl_pop(&objalloc->opl); \ + TYPE *obj; \ + int rv; \ + \ + if (!oplent) { \ + rv = ngtcp2_balloc_get(&objalloc->balloc, (void **)&obj, len); \ + if (rv != 0) { \ + return NULL; \ + } \ + \ + return obj; \ + } \ + \ + return ngtcp2_struct_of(oplent, TYPE, OPLENTFIELD); \ + } \ + \ + inline static void ngtcp2_objalloc_##NAME##_release( \ + ngtcp2_objalloc *objalloc, TYPE *obj) { \ + ngtcp2_opl_push(&objalloc->opl, &obj->OPLENTFIELD); \ + } +#else /* NOMEMPOOL */ +# define ngtcp2_objalloc_def(NAME, TYPE, OPLENTFIELD) \ + inline static void ngtcp2_objalloc_##NAME##_init( \ + ngtcp2_objalloc *objalloc, size_t nmemb, const ngtcp2_mem *mem) { \ + ngtcp2_objalloc_init( \ + objalloc, ((sizeof(TYPE) + 0xfu) & ~(uintptr_t)0xfu) * nmemb, mem); \ + } \ + \ + inline static TYPE *ngtcp2_objalloc_##NAME##_get( \ + ngtcp2_objalloc *objalloc) { \ + return ngtcp2_mem_malloc(objalloc->balloc.mem, sizeof(TYPE)); \ + } \ + \ + inline static TYPE *ngtcp2_objalloc_##NAME##_len_get( \ + ngtcp2_objalloc *objalloc, size_t len) { \ + return ngtcp2_mem_malloc(objalloc->balloc.mem, len); \ + } \ + \ + inline static void ngtcp2_objalloc_##NAME##_release( \ + ngtcp2_objalloc *objalloc, TYPE *obj) { \ + ngtcp2_mem_free(objalloc->balloc.mem, obj); \ + } +#endif /* NOMEMPOOL */ + +#endif /* NGTCP2_OBJALLOC_H */ diff --git a/lib/ngtcp2_opl.c b/lib/ngtcp2_opl.c new file mode 100644 index 0000000..a29361c --- /dev/null +++ b/lib/ngtcp2_opl.c @@ -0,0 +1,46 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_opl.h" + +void ngtcp2_opl_init(ngtcp2_opl *opl) { opl->head = NULL; } + +void ngtcp2_opl_push(ngtcp2_opl *opl, ngtcp2_opl_entry *ent) { + ent->next = opl->head; + opl->head = ent; +} + +ngtcp2_opl_entry *ngtcp2_opl_pop(ngtcp2_opl *opl) { + ngtcp2_opl_entry *ent = opl->head; + + if (!ent) { + return NULL; + } + + opl->head = ent->next; + + return ent; +} + +void ngtcp2_opl_clear(ngtcp2_opl *opl) { opl->head = NULL; } diff --git a/lib/ngtcp2_opl.h b/lib/ngtcp2_opl.h new file mode 100644 index 0000000..714aa36 --- /dev/null +++ b/lib/ngtcp2_opl.h @@ -0,0 +1,65 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_OPL_H +#define NGTCP2_OPL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +typedef struct ngtcp2_opl_entry ngtcp2_opl_entry; + +struct ngtcp2_opl_entry { + ngtcp2_opl_entry *next; +}; + +/* + * ngtcp2_opl is an object memory pool. + */ +typedef struct ngtcp2_opl { + ngtcp2_opl_entry *head; +} ngtcp2_opl; + +/* + * ngtcp2_opl_init initializes |opl|. + */ +void ngtcp2_opl_init(ngtcp2_opl *opl); + +/* + * ngtcp2_opl_push inserts |ent| to |opl| head. + */ +void ngtcp2_opl_push(ngtcp2_opl *opl, ngtcp2_opl_entry *ent); + +/* + * ngtcp2_opl_pop removes the first ngtcp2_opl_entry from |opl| and + * returns it. If |opl| does not have any entry, it returns NULL. + */ +ngtcp2_opl_entry *ngtcp2_opl_pop(ngtcp2_opl *opl); + +void ngtcp2_opl_clear(ngtcp2_opl *opl); + +#endif /* NGTCP2_OPL_H */ diff --git a/lib/ngtcp2_path.c b/lib/ngtcp2_path.c new file mode 100644 index 0000000..8323873 --- /dev/null +++ b/lib/ngtcp2_path.c @@ -0,0 +1,77 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_path.h" + +#include <string.h> + +#include "ngtcp2_addr.h" + +void ngtcp2_path_init(ngtcp2_path *path, const ngtcp2_addr *local, + const ngtcp2_addr *remote) { + path->local = *local; + path->remote = *remote; +} + +void ngtcp2_path_copy(ngtcp2_path *dest, const ngtcp2_path *src) { + ngtcp2_addr_copy(&dest->local, &src->local); + ngtcp2_addr_copy(&dest->remote, &src->remote); + dest->user_data = src->user_data; +} + +int ngtcp2_path_eq(const ngtcp2_path *a, const ngtcp2_path *b) { + return ngtcp2_addr_eq(&a->local, &b->local) && + ngtcp2_addr_eq(&a->remote, &b->remote); +} + +void ngtcp2_path_storage_init(ngtcp2_path_storage *ps, + const ngtcp2_sockaddr *local_addr, + ngtcp2_socklen local_addrlen, + const ngtcp2_sockaddr *remote_addr, + ngtcp2_socklen remote_addrlen, void *user_data) { + ngtcp2_addr_init(&ps->path.local, (const ngtcp2_sockaddr *)&ps->local_addrbuf, + 0); + ngtcp2_addr_init(&ps->path.remote, + (const ngtcp2_sockaddr *)&ps->remote_addrbuf, 0); + + ngtcp2_addr_copy_byte(&ps->path.local, local_addr, local_addrlen); + ngtcp2_addr_copy_byte(&ps->path.remote, remote_addr, remote_addrlen); + + ps->path.user_data = user_data; +} + +void ngtcp2_path_storage_init2(ngtcp2_path_storage *ps, + const ngtcp2_path *path) { + ngtcp2_path_storage_init(ps, path->local.addr, path->local.addrlen, + path->remote.addr, path->remote.addrlen, + path->user_data); +} + +void ngtcp2_path_storage_zero(ngtcp2_path_storage *ps) { + ngtcp2_addr_init(&ps->path.local, (const ngtcp2_sockaddr *)&ps->local_addrbuf, + 0); + ngtcp2_addr_init(&ps->path.remote, + (const ngtcp2_sockaddr *)&ps->remote_addrbuf, 0); + ps->path.user_data = NULL; +} diff --git a/lib/ngtcp2_path.h b/lib/ngtcp2_path.h new file mode 100644 index 0000000..0c360e9 --- /dev/null +++ b/lib/ngtcp2_path.h @@ -0,0 +1,49 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_PATH_H +#define NGTCP2_PATH_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * ngtcp2_path_init initializes |path| with the given addresses. Note + * that the buffer pointed by local->addr and remote->addr are not + * copied. Their pointer values are assigned instead. + */ +void ngtcp2_path_init(ngtcp2_path *path, const ngtcp2_addr *local, + const ngtcp2_addr *remote); + +/* + * ngtcp2_path_storage_init2 initializes |ps| using |path| as initial + * data. + */ +void ngtcp2_path_storage_init2(ngtcp2_path_storage *ps, + const ngtcp2_path *path); + +#endif /* NGTCP2_PATH_H */ diff --git a/lib/ngtcp2_pkt.c b/lib/ngtcp2_pkt.c new file mode 100644 index 0000000..f76e07b --- /dev/null +++ b/lib/ngtcp2_pkt.c @@ -0,0 +1,2527 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_pkt.h" + +#include <assert.h> +#include <string.h> + +#include "ngtcp2_conv.h" +#include "ngtcp2_str.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_cid.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_unreachable.h" +#include "ngtcp2_str.h" + +int ngtcp2_pkt_chain_new(ngtcp2_pkt_chain **ppc, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, ngtcp2_tstamp ts, + const ngtcp2_mem *mem) { + *ppc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pkt_chain) + pktlen); + if (*ppc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_path_storage_init2(&(*ppc)->path, path); + (*ppc)->pi = *pi; + (*ppc)->next = NULL; + (*ppc)->pkt = (uint8_t *)(*ppc) + sizeof(ngtcp2_pkt_chain); + (*ppc)->pktlen = pktlen; + (*ppc)->dgramlen = dgramlen; + (*ppc)->ts = ts; + + memcpy((*ppc)->pkt, pkt, pktlen); + + return 0; +} + +void ngtcp2_pkt_chain_del(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, pc); +} + +int ngtcp2_pkt_decode_version_cid(ngtcp2_version_cid *dest, const uint8_t *data, + size_t datalen, size_t short_dcidlen) { + size_t len; + uint32_t version; + size_t dcidlen, scidlen; + int supported_version; + + assert(datalen); + + if (data[0] & NGTCP2_HEADER_FORM_BIT) { + /* 1 byte (Header Form, Fixed Bit, Long Packet Type, Type-Specific bits) + * 4 bytes Version + * 1 byte DCID Length + * 1 byte SCID Length + */ + len = 1 + 4 + 1 + 1; + if (datalen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dcidlen = data[5]; + len += dcidlen; + if (datalen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + scidlen = data[5 + 1 + dcidlen]; + len += scidlen; + if (datalen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + ngtcp2_get_uint32(&version, &data[1]); + + supported_version = ngtcp2_is_supported_version(version); + + if (supported_version && + (dcidlen > NGTCP2_MAX_CIDLEN || scidlen > NGTCP2_MAX_CIDLEN)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (version && !supported_version && + datalen < NGTCP2_MAX_UDP_PAYLOAD_SIZE) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dest->version = version; + dest->dcid = &data[6]; + dest->dcidlen = dcidlen; + dest->scid = &data[6 + dcidlen + 1]; + dest->scidlen = scidlen; + + if (!version) { + /* VN */ + return 0; + } + + if (!supported_version) { + return NGTCP2_ERR_VERSION_NEGOTIATION; + } + return 0; + } + + assert(short_dcidlen <= NGTCP2_MAX_CIDLEN); + + len = 1 + short_dcidlen; + if (datalen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dest->version = 0; + dest->dcid = &data[1]; + dest->dcidlen = short_dcidlen; + dest->scid = NULL; + dest->scidlen = 0; + + return 0; +} + +void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type, + const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + int64_t pkt_num, size_t pkt_numlen, uint32_t version, + size_t len) { + hd->flags = flags; + hd->type = type; + if (dcid) { + hd->dcid = *dcid; + } else { + ngtcp2_cid_zero(&hd->dcid); + } + if (scid) { + hd->scid = *scid; + } else { + ngtcp2_cid_zero(&hd->scid); + } + hd->pkt_num = pkt_num; + hd->token.base = NULL; + hd->token.len = 0; + hd->pkt_numlen = pkt_numlen; + hd->version = version; + hd->len = len; +} + +static int has_mask(uint8_t b, uint8_t mask) { return (b & mask) == mask; } + +ngtcp2_ssize ngtcp2_pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen) { + uint8_t type; + uint32_t version; + size_t dcil, scil; + const uint8_t *p; + size_t len = 0; + size_t n; + size_t ntokenlen = 0; + const uint8_t *token = NULL; + size_t tokenlen = 0; + uint64_t vi; + uint8_t flags = NGTCP2_PKT_FLAG_LONG_FORM; + + if (pktlen < 5) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (!(pkt[0] & NGTCP2_HEADER_FORM_BIT)) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + ngtcp2_get_uint32(&version, &pkt[1]); + + if (version == 0) { + type = NGTCP2_PKT_VERSION_NEGOTIATION; + /* Version Negotiation is not a long header packet. */ + flags = NGTCP2_PKT_FLAG_NONE; + /* This must be Version Negotiation packet which lacks packet + number and payload length fields. */ + len = 5 + 2; + } else { + if (!(pkt[0] & NGTCP2_FIXED_BIT_MASK)) { + flags |= NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR; + } + + type = ngtcp2_pkt_get_type_long(version, pkt[0]); + switch (type) { + case 0: + return NGTCP2_ERR_INVALID_ARGUMENT; + case NGTCP2_PKT_INITIAL: + len = 1 /* Token Length */ + NGTCP2_MIN_LONG_HEADERLEN - + 1; /* Cut packet number field */ + break; + case NGTCP2_PKT_RETRY: + /* Retry packet does not have packet number and length fields */ + len = 5 + 2; + break; + case NGTCP2_PKT_HANDSHAKE: + case NGTCP2_PKT_0RTT: + len = NGTCP2_MIN_LONG_HEADERLEN - 1; /* Cut packet number field */ + break; + default: + /* Unreachable */ + ngtcp2_unreachable(); + } + } + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p = &pkt[5]; + dcil = *p; + if (dcil > NGTCP2_MAX_CIDLEN) { + /* QUIC v1 implementation never expect to receive CID length more + than NGTCP2_MAX_CIDLEN. */ + return NGTCP2_ERR_INVALID_ARGUMENT; + } + len += dcil; + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p += 1 + dcil; + scil = *p; + if (scil > NGTCP2_MAX_CIDLEN) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + len += scil; + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p += 1 + scil; + + if (type == NGTCP2_PKT_INITIAL) { + /* Token Length */ + ntokenlen = ngtcp2_get_uvarintlen(p); + len += ntokenlen - 1; + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (pktlen - len < vi) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + tokenlen = (size_t)vi; + len += tokenlen; + + if (tokenlen) { + token = p; + } + + p += tokenlen; + } + + switch (type) { + case NGTCP2_PKT_RETRY: + break; + default: + if (!(flags & NGTCP2_PKT_FLAG_LONG_FORM)) { + assert(type == NGTCP2_PKT_VERSION_NEGOTIATION); + /* Version Negotiation is not a long header packet. */ + break; + } + + /* Length */ + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + } + + dest->flags = flags; + dest->type = type; + dest->version = version; + dest->pkt_num = 0; + dest->pkt_numlen = 0; + + p = &pkt[6]; + ngtcp2_cid_init(&dest->dcid, p, dcil); + p += dcil + 1; + ngtcp2_cid_init(&dest->scid, p, scil); + p += scil; + + dest->token.base = (uint8_t *)token; + dest->token.len = tokenlen; + p += ntokenlen + tokenlen; + + switch (type) { + case NGTCP2_PKT_RETRY: + dest->len = 0; + break; + default: + if (!(flags & NGTCP2_PKT_FLAG_LONG_FORM)) { + assert(type == NGTCP2_PKT_VERSION_NEGOTIATION); + /* Version Negotiation is not a long header packet. */ + dest->len = 0; + break; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (vi > SIZE_MAX) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + dest->len = (size_t)vi; + } + + assert((size_t)(p - pkt) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen, size_t dcidlen) { + size_t len = 1 + dcidlen; + const uint8_t *p = pkt; + uint8_t flags = NGTCP2_PKT_FLAG_NONE; + + assert(dcidlen <= NGTCP2_MAX_CIDLEN); + + if (pktlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (pkt[0] & NGTCP2_HEADER_FORM_BIT) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + if (!(pkt[0] & NGTCP2_FIXED_BIT_MASK)) { + flags |= NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR; + } + + p = &pkt[1]; + + dest->type = NGTCP2_PKT_1RTT; + + ngtcp2_cid_init(&dest->dcid, p, dcidlen); + p += dcidlen; + + /* Set 0 to SCID so that we don't accidentally reference it and gets + garbage. */ + ngtcp2_cid_zero(&dest->scid); + + dest->flags = flags; + dest->version = 0; + dest->len = 0; + dest->pkt_num = 0; + dest->pkt_numlen = 0; + dest->token.base = NULL; + dest->token.len = 0; + + assert((size_t)(p - pkt) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen, + const ngtcp2_pkt_hd *hd) { + uint8_t *p; + size_t len = NGTCP2_MIN_LONG_HEADERLEN + hd->dcid.datalen + hd->scid.datalen - + 2; /* NGTCP2_MIN_LONG_HEADERLEN includes 1 byte for + len and 1 byte for packet number. */ + + if (hd->type != NGTCP2_PKT_RETRY) { + len += NGTCP2_PKT_LENGTHLEN /* Length */ + hd->pkt_numlen; + } + + if (hd->type == NGTCP2_PKT_INITIAL) { + len += ngtcp2_put_uvarintlen(hd->token.len) + hd->token.len; + } + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p = (uint8_t)(NGTCP2_HEADER_FORM_BIT | + (ngtcp2_pkt_versioned_type(hd->version, hd->type) << 4) | + (uint8_t)(hd->pkt_numlen - 1)); + if (!(hd->flags & NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR)) { + *p |= NGTCP2_FIXED_BIT_MASK; + } + + ++p; + + p = ngtcp2_put_uint32be(p, hd->version); + *p++ = (uint8_t)hd->dcid.datalen; + if (hd->dcid.datalen) { + p = ngtcp2_cpymem(p, hd->dcid.data, hd->dcid.datalen); + } + *p++ = (uint8_t)hd->scid.datalen; + if (hd->scid.datalen) { + p = ngtcp2_cpymem(p, hd->scid.data, hd->scid.datalen); + } + + if (hd->type == NGTCP2_PKT_INITIAL) { + p = ngtcp2_put_uvarint(p, hd->token.len); + if (hd->token.len) { + p = ngtcp2_cpymem(p, hd->token.base, hd->token.len); + } + } + + if (hd->type != NGTCP2_PKT_RETRY) { + p = ngtcp2_put_uvarint30(p, (uint32_t)hd->len); + p = ngtcp2_put_pkt_num(p, hd->pkt_num, hd->pkt_numlen); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen, + const ngtcp2_pkt_hd *hd) { + uint8_t *p; + size_t len = 1 + hd->dcid.datalen + hd->pkt_numlen; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p = (uint8_t)(hd->pkt_numlen - 1); + if (!(hd->flags & NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR)) { + *p |= NGTCP2_FIXED_BIT_MASK; + } + if (hd->flags & NGTCP2_PKT_FLAG_KEY_PHASE) { + *p |= NGTCP2_SHORT_KEY_PHASE_BIT; + } + + ++p; + + if (hd->dcid.datalen) { + p = ngtcp2_cpymem(p, hd->dcid.data, hd->dcid.datalen); + } + + p = ngtcp2_put_pkt_num(p, hd->pkt_num, hd->pkt_numlen); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload, + size_t payloadlen) { + uint8_t type; + + if (payloadlen == 0) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + switch (type) { + case NGTCP2_FRAME_PADDING: + return ngtcp2_pkt_decode_padding_frame(&dest->padding, payload, payloadlen); + case NGTCP2_FRAME_RESET_STREAM: + return ngtcp2_pkt_decode_reset_stream_frame(&dest->reset_stream, payload, + payloadlen); + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + return ngtcp2_pkt_decode_connection_close_frame(&dest->connection_close, + payload, payloadlen); + case NGTCP2_FRAME_MAX_DATA: + return ngtcp2_pkt_decode_max_data_frame(&dest->max_data, payload, + payloadlen); + case NGTCP2_FRAME_MAX_STREAM_DATA: + return ngtcp2_pkt_decode_max_stream_data_frame(&dest->max_stream_data, + payload, payloadlen); + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + return ngtcp2_pkt_decode_max_streams_frame(&dest->max_streams, payload, + payloadlen); + case NGTCP2_FRAME_PING: + return ngtcp2_pkt_decode_ping_frame(&dest->ping, payload, payloadlen); + case NGTCP2_FRAME_DATA_BLOCKED: + return ngtcp2_pkt_decode_data_blocked_frame(&dest->data_blocked, payload, + payloadlen); + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + return ngtcp2_pkt_decode_stream_data_blocked_frame( + &dest->stream_data_blocked, payload, payloadlen); + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + return ngtcp2_pkt_decode_streams_blocked_frame(&dest->streams_blocked, + payload, payloadlen); + case NGTCP2_FRAME_NEW_CONNECTION_ID: + return ngtcp2_pkt_decode_new_connection_id_frame(&dest->new_connection_id, + payload, payloadlen); + case NGTCP2_FRAME_STOP_SENDING: + return ngtcp2_pkt_decode_stop_sending_frame(&dest->stop_sending, payload, + payloadlen); + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + return ngtcp2_pkt_decode_ack_frame(&dest->ack, payload, payloadlen); + case NGTCP2_FRAME_PATH_CHALLENGE: + return ngtcp2_pkt_decode_path_challenge_frame(&dest->path_challenge, + payload, payloadlen); + case NGTCP2_FRAME_PATH_RESPONSE: + return ngtcp2_pkt_decode_path_response_frame(&dest->path_response, payload, + payloadlen); + case NGTCP2_FRAME_CRYPTO: + return ngtcp2_pkt_decode_crypto_frame(&dest->crypto, payload, payloadlen); + case NGTCP2_FRAME_NEW_TOKEN: + return ngtcp2_pkt_decode_new_token_frame(&dest->new_token, payload, + payloadlen); + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + return ngtcp2_pkt_decode_retire_connection_id_frame( + &dest->retire_connection_id, payload, payloadlen); + case NGTCP2_FRAME_HANDSHAKE_DONE: + return ngtcp2_pkt_decode_handshake_done_frame(&dest->handshake_done, + payload, payloadlen); + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + return ngtcp2_pkt_decode_datagram_frame(&dest->datagram, payload, + payloadlen); + default: + if (has_mask(type, NGTCP2_FRAME_STREAM)) { + return ngtcp2_pkt_decode_stream_frame(&dest->stream, payload, payloadlen); + } + return NGTCP2_ERR_FRAME_ENCODING; + } +} + +ngtcp2_ssize ngtcp2_pkt_decode_stream_frame(ngtcp2_stream *dest, + const uint8_t *payload, + size_t payloadlen) { + uint8_t type; + size_t len = 1 + 1; + const uint8_t *p; + size_t datalen; + size_t ndatalen = 0; + size_t n; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + if (type & NGTCP2_STREAM_OFF_BIT) { + ++len; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + } + + if (type & NGTCP2_STREAM_LEN_BIT) { + ++len; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + ndatalen = ngtcp2_get_uvarintlen(p); + len += ndatalen - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + /* p = */ ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + datalen = (size_t)vi; + len += datalen; + } else { + len = payloadlen; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_STREAM; + dest->flags = (uint8_t)(type & ~NGTCP2_FRAME_STREAM); + dest->fin = (type & NGTCP2_STREAM_FIN_BIT) != 0; + p = ngtcp2_get_varint(&dest->stream_id, p); + + if (type & NGTCP2_STREAM_OFF_BIT) { + p = ngtcp2_get_uvarint(&dest->offset, p); + } else { + dest->offset = 0; + } + + if (type & NGTCP2_STREAM_LEN_BIT) { + p += ndatalen; + } else { + datalen = payloadlen - (size_t)(p - payload); + } + + if (datalen) { + dest->data[0].len = datalen; + dest->data[0].base = (uint8_t *)p; + dest->datacnt = 1; + p += datalen; + } else { + dest->datacnt = 0; + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_ack_frame(ngtcp2_ack *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t rangecnt, max_rangecnt; + size_t nrangecnt; + size_t len = 1 + 1 + 1 + 1 + 1; + const uint8_t *p; + size_t i, j; + ngtcp2_ack_range *range; + size_t n; + uint8_t type; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + p = payload + 1; + + /* Largest Acknowledged */ + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + /* ACK Delay */ + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + /* ACK Range Count */ + nrangecnt = ngtcp2_get_uvarintlen(p); + len += nrangecnt - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (vi > SIZE_MAX / (1 + 1) || payloadlen - len < vi * (1 + 1)) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + rangecnt = (size_t)vi; + len += rangecnt * (1 + 1); + + /* First ACK Range */ + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + for (i = 0; i < rangecnt; ++i) { + /* Gap, and Additional ACK Range */ + for (j = 0; j < 2; ++j) { + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + } + } + + if (type == NGTCP2_FRAME_ACK_ECN) { + len += 3; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + for (i = 0; i < 3; ++i) { + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + } + } + + /* TODO We might not decode all ranges. It could be very large. */ + max_rangecnt = ngtcp2_min(NGTCP2_MAX_ACK_RANGES, rangecnt); + + p = payload + 1; + + dest->type = type; + p = ngtcp2_get_varint(&dest->largest_ack, p); + p = ngtcp2_get_uvarint(&dest->ack_delay, p); + /* This value will be assigned in the upper layer. */ + dest->ack_delay_unscaled = 0; + dest->rangecnt = max_rangecnt; + p += nrangecnt; + p = ngtcp2_get_uvarint(&dest->first_ack_range, p); + + for (i = 0; i < max_rangecnt; ++i) { + range = &dest->ranges[i]; + p = ngtcp2_get_uvarint(&range->gap, p); + p = ngtcp2_get_uvarint(&range->len, p); + } + for (i = max_rangecnt; i < rangecnt; ++i) { + p += ngtcp2_get_uvarintlen(p); + p += ngtcp2_get_uvarintlen(p); + } + + if (type == NGTCP2_FRAME_ACK_ECN) { + p = ngtcp2_get_uvarint(&dest->ecn.ect0, p); + p = ngtcp2_get_uvarint(&dest->ecn.ect1, p); + p = ngtcp2_get_uvarint(&dest->ecn.ce, p); + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_padding_frame(ngtcp2_padding *dest, + const uint8_t *payload, + size_t payloadlen) { + const uint8_t *p, *ep; + + assert(payloadlen > 0); + + p = payload + 1; + ep = payload + payloadlen; + + for (; p != ep && *p == NGTCP2_FRAME_PADDING; ++p) + ; + + dest->type = NGTCP2_FRAME_PADDING; + dest->len = (size_t)(p - payload); + + return (ngtcp2_ssize)dest->len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_reset_stream_frame(ngtcp2_reset_stream *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1 + 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + p += n; + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + p += n; + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_RESET_STREAM; + p = ngtcp2_get_varint(&dest->stream_id, p); + p = ngtcp2_get_uvarint(&dest->app_error_code, p); + p = ngtcp2_get_uvarint(&dest->final_size, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_connection_close_frame( + ngtcp2_connection_close *dest, const uint8_t *payload, size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t reasonlen; + size_t nreasonlen; + size_t n; + uint8_t type; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + if (type == NGTCP2_FRAME_CONNECTION_CLOSE) { + ++len; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + } + + nreasonlen = ngtcp2_get_uvarintlen(p); + len += nreasonlen - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + reasonlen = (size_t)vi; + len += reasonlen; + + p = payload + 1; + + dest->type = type; + p = ngtcp2_get_uvarint(&dest->error_code, p); + if (type == NGTCP2_FRAME_CONNECTION_CLOSE) { + p = ngtcp2_get_uvarint(&dest->frame_type, p); + } else { + dest->frame_type = 0; + } + dest->reasonlen = reasonlen; + p += nreasonlen; + if (reasonlen == 0) { + dest->reason = NULL; + } else { + dest->reason = (uint8_t *)p; + p += reasonlen; + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_max_data_frame(ngtcp2_max_data *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = NGTCP2_FRAME_MAX_DATA; + p = ngtcp2_get_uvarint(&dest->max_data, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_max_stream_data_frame( + ngtcp2_max_stream_data *dest, const uint8_t *payload, size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_MAX_STREAM_DATA; + p = ngtcp2_get_varint(&dest->stream_id, p); + p = ngtcp2_get_uvarint(&dest->max_stream_data, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_max_streams_frame(ngtcp2_max_streams *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = payload[0]; + p = ngtcp2_get_uvarint(&dest->max_streams, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_ping_frame(ngtcp2_ping *dest, + const uint8_t *payload, + size_t payloadlen) { + (void)payload; + (void)payloadlen; + + dest->type = NGTCP2_FRAME_PING; + return 1; +} + +ngtcp2_ssize ngtcp2_pkt_decode_data_blocked_frame(ngtcp2_data_blocked *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = NGTCP2_FRAME_DATA_BLOCKED; + p = ngtcp2_get_uvarint(&dest->offset, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_decode_stream_data_blocked_frame(ngtcp2_stream_data_blocked *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_STREAM_DATA_BLOCKED; + p = ngtcp2_get_varint(&dest->stream_id, p); + p = ngtcp2_get_uvarint(&dest->offset, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_streams_blocked_frame( + ngtcp2_streams_blocked *dest, const uint8_t *payload, size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = payload[0]; + p = ngtcp2_get_uvarint(&dest->max_streams, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_new_connection_id_frame( + ngtcp2_new_connection_id *dest, const uint8_t *payload, size_t payloadlen) { + size_t len = 1 + 1 + 1 + 1 + 16; + const uint8_t *p; + size_t n; + size_t cil; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + cil = *p; + if (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + len += cil; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_NEW_CONNECTION_ID; + p = ngtcp2_get_uvarint(&dest->seq, p); + p = ngtcp2_get_uvarint(&dest->retire_prior_to, p); + ++p; + ngtcp2_cid_init(&dest->cid, p, cil); + p += cil; + p = ngtcp2_get_bytes(dest->stateless_reset_token, p, + NGTCP2_STATELESS_RESET_TOKENLEN); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_stop_sending_frame(ngtcp2_stop_sending *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + p += n; + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_STOP_SENDING; + p = ngtcp2_get_varint(&dest->stream_id, p); + p = ngtcp2_get_uvarint(&dest->app_error_code, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_path_challenge_frame(ngtcp2_path_challenge *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 8; + const uint8_t *p; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_PATH_CHALLENGE; + ngtcp2_cpymem(dest->data, p, sizeof(dest->data)); + p += sizeof(dest->data); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_path_response_frame(ngtcp2_path_response *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 8; + const uint8_t *p; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + dest->type = NGTCP2_FRAME_PATH_RESPONSE; + ngtcp2_cpymem(dest->data, p, sizeof(dest->data)); + p += sizeof(dest->data); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_crypto_frame(ngtcp2_crypto *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1 + 1; + const uint8_t *p; + size_t datalen; + size_t ndatalen; + size_t n; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p += n; + + ndatalen = ngtcp2_get_uvarintlen(p); + len += ndatalen - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + datalen = (size_t)vi; + len += datalen; + + p = payload + 1; + + dest->type = NGTCP2_FRAME_CRYPTO; + p = ngtcp2_get_uvarint(&dest->offset, p); + dest->data[0].len = datalen; + p += ndatalen; + if (dest->data[0].len) { + dest->data[0].base = (uint8_t *)p; + p += dest->data[0].len; + dest->datacnt = 1; + } else { + dest->data[0].base = NULL; + dest->datacnt = 0; + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_new_token_frame(ngtcp2_new_token *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + size_t datalen; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + datalen = (size_t)vi; + len += datalen; + + dest->type = NGTCP2_FRAME_NEW_TOKEN; + dest->token.len = datalen; + dest->token.base = (uint8_t *)p; + p += dest->token.len; + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_decode_retire_connection_id_frame(ngtcp2_retire_connection_id *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1 + 1; + const uint8_t *p; + size_t n; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = payload + 1; + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + dest->type = NGTCP2_FRAME_RETIRE_CONNECTION_ID; + p = ngtcp2_get_uvarint(&dest->seq, p); + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_decode_handshake_done_frame(ngtcp2_handshake_done *dest, + const uint8_t *payload, + size_t payloadlen) { + (void)payload; + (void)payloadlen; + + dest->type = NGTCP2_FRAME_HANDSHAKE_DONE; + return 1; +} + +ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest, + const uint8_t *payload, + size_t payloadlen) { + size_t len = 1; + const uint8_t *p; + uint8_t type; + size_t datalen; + size_t n; + uint64_t vi; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + type = payload[0]; + + p = payload + 1; + + switch (type) { + case NGTCP2_FRAME_DATAGRAM: + datalen = payloadlen - 1; + len = payloadlen; + break; + case NGTCP2_FRAME_DATAGRAM_LEN: + ++len; + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + n = ngtcp2_get_uvarintlen(p); + len += n - 1; + + if (payloadlen < len) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + p = ngtcp2_get_uvarint(&vi, p); + if (payloadlen - len < vi) { + return NGTCP2_ERR_FRAME_ENCODING; + } + + datalen = (size_t)vi; + len += datalen; + break; + default: + ngtcp2_unreachable(); + } + + dest->type = type; + + if (datalen == 0) { + dest->datacnt = 0; + dest->data = NULL; + } else { + dest->datacnt = 1; + dest->data = dest->rdata; + dest->rdata[0].len = datalen; + + dest->rdata[0].base = (uint8_t *)p; + p += datalen; + } + + assert((size_t)(p - payload) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_frame(uint8_t *out, size_t outlen, + ngtcp2_frame *fr) { + switch (fr->type) { + case NGTCP2_FRAME_STREAM: + return ngtcp2_pkt_encode_stream_frame(out, outlen, &fr->stream); + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + return ngtcp2_pkt_encode_ack_frame(out, outlen, &fr->ack); + case NGTCP2_FRAME_PADDING: + return ngtcp2_pkt_encode_padding_frame(out, outlen, &fr->padding); + case NGTCP2_FRAME_RESET_STREAM: + return ngtcp2_pkt_encode_reset_stream_frame(out, outlen, &fr->reset_stream); + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + return ngtcp2_pkt_encode_connection_close_frame(out, outlen, + &fr->connection_close); + case NGTCP2_FRAME_MAX_DATA: + return ngtcp2_pkt_encode_max_data_frame(out, outlen, &fr->max_data); + case NGTCP2_FRAME_MAX_STREAM_DATA: + return ngtcp2_pkt_encode_max_stream_data_frame(out, outlen, + &fr->max_stream_data); + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + return ngtcp2_pkt_encode_max_streams_frame(out, outlen, &fr->max_streams); + case NGTCP2_FRAME_PING: + return ngtcp2_pkt_encode_ping_frame(out, outlen, &fr->ping); + case NGTCP2_FRAME_DATA_BLOCKED: + return ngtcp2_pkt_encode_data_blocked_frame(out, outlen, &fr->data_blocked); + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + return ngtcp2_pkt_encode_stream_data_blocked_frame( + out, outlen, &fr->stream_data_blocked); + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + return ngtcp2_pkt_encode_streams_blocked_frame(out, outlen, + &fr->streams_blocked); + case NGTCP2_FRAME_NEW_CONNECTION_ID: + return ngtcp2_pkt_encode_new_connection_id_frame(out, outlen, + &fr->new_connection_id); + case NGTCP2_FRAME_STOP_SENDING: + return ngtcp2_pkt_encode_stop_sending_frame(out, outlen, &fr->stop_sending); + case NGTCP2_FRAME_PATH_CHALLENGE: + return ngtcp2_pkt_encode_path_challenge_frame(out, outlen, + &fr->path_challenge); + case NGTCP2_FRAME_PATH_RESPONSE: + return ngtcp2_pkt_encode_path_response_frame(out, outlen, + &fr->path_response); + case NGTCP2_FRAME_CRYPTO: + return ngtcp2_pkt_encode_crypto_frame(out, outlen, &fr->crypto); + case NGTCP2_FRAME_NEW_TOKEN: + return ngtcp2_pkt_encode_new_token_frame(out, outlen, &fr->new_token); + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + return ngtcp2_pkt_encode_retire_connection_id_frame( + out, outlen, &fr->retire_connection_id); + case NGTCP2_FRAME_HANDSHAKE_DONE: + return ngtcp2_pkt_encode_handshake_done_frame(out, outlen, + &fr->handshake_done); + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + return ngtcp2_pkt_encode_datagram_frame(out, outlen, &fr->datagram); + default: + return NGTCP2_ERR_INVALID_ARGUMENT; + } +} + +ngtcp2_ssize ngtcp2_pkt_encode_stream_frame(uint8_t *out, size_t outlen, + ngtcp2_stream *fr) { + size_t len = 1; + uint8_t flags = NGTCP2_STREAM_LEN_BIT; + uint8_t *p; + size_t i; + size_t datalen = 0; + + if (fr->fin) { + flags |= NGTCP2_STREAM_FIN_BIT; + } + + if (fr->offset) { + flags |= NGTCP2_STREAM_OFF_BIT; + len += ngtcp2_put_uvarintlen(fr->offset); + } + + len += ngtcp2_put_uvarintlen((uint64_t)fr->stream_id); + + for (i = 0; i < fr->datacnt; ++i) { + datalen += fr->data[i].len; + } + + len += ngtcp2_put_uvarintlen(datalen); + len += datalen; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = flags | NGTCP2_FRAME_STREAM; + + fr->flags = flags; + + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + + if (fr->offset) { + p = ngtcp2_put_uvarint(p, fr->offset); + } + + p = ngtcp2_put_uvarint(p, datalen); + + for (i = 0; i < fr->datacnt; ++i) { + assert(fr->data[i].len); + assert(fr->data[i].base); + p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_ack_frame(uint8_t *out, size_t outlen, + ngtcp2_ack *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->largest_ack) + + ngtcp2_put_uvarintlen(fr->ack_delay) + + ngtcp2_put_uvarintlen(fr->rangecnt) + + ngtcp2_put_uvarintlen(fr->first_ack_range); + uint8_t *p; + size_t i; + const ngtcp2_ack_range *range; + + for (i = 0; i < fr->rangecnt; ++i) { + range = &fr->ranges[i]; + len += ngtcp2_put_uvarintlen(range->gap); + len += ngtcp2_put_uvarintlen(range->len); + } + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { + len += ngtcp2_put_uvarintlen(fr->ecn.ect0) + + ngtcp2_put_uvarintlen(fr->ecn.ect1) + + ngtcp2_put_uvarintlen(fr->ecn.ce); + } + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = fr->type; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->largest_ack); + p = ngtcp2_put_uvarint(p, fr->ack_delay); + p = ngtcp2_put_uvarint(p, fr->rangecnt); + p = ngtcp2_put_uvarint(p, fr->first_ack_range); + + for (i = 0; i < fr->rangecnt; ++i) { + range = &fr->ranges[i]; + p = ngtcp2_put_uvarint(p, range->gap); + p = ngtcp2_put_uvarint(p, range->len); + } + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { + p = ngtcp2_put_uvarint(p, fr->ecn.ect0); + p = ngtcp2_put_uvarint(p, fr->ecn.ect1); + p = ngtcp2_put_uvarint(p, fr->ecn.ce); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_padding_frame(uint8_t *out, size_t outlen, + const ngtcp2_padding *fr) { + if (outlen < fr->len) { + return NGTCP2_ERR_NOBUF; + } + + memset(out, 0, fr->len); + + return (ngtcp2_ssize)fr->len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_reset_stream_frame(uint8_t *out, size_t outlen, + const ngtcp2_reset_stream *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) + + ngtcp2_put_uvarintlen(fr->app_error_code) + + ngtcp2_put_uvarintlen(fr->final_size); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_RESET_STREAM; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + p = ngtcp2_put_uvarint(p, fr->app_error_code); + p = ngtcp2_put_uvarint(p, fr->final_size); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_connection_close_frame(uint8_t *out, size_t outlen, + const ngtcp2_connection_close *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->error_code) + + (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE + ? ngtcp2_put_uvarintlen(fr->frame_type) + : 0) + + ngtcp2_put_uvarintlen(fr->reasonlen) + fr->reasonlen; + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = fr->type; + p = ngtcp2_put_uvarint(p, fr->error_code); + if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) { + p = ngtcp2_put_uvarint(p, fr->frame_type); + } + p = ngtcp2_put_uvarint(p, fr->reasonlen); + if (fr->reasonlen) { + p = ngtcp2_cpymem(p, fr->reason, fr->reasonlen); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_max_data_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_data *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_data); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_MAX_DATA; + p = ngtcp2_put_uvarint(p, fr->max_data); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_max_stream_data_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_stream_data *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) + + ngtcp2_put_uvarintlen(fr->max_stream_data); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_MAX_STREAM_DATA; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + p = ngtcp2_put_uvarint(p, fr->max_stream_data); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_max_streams_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_streams *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_streams); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = fr->type; + p = ngtcp2_put_uvarint(p, fr->max_streams); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_ping_frame(uint8_t *out, size_t outlen, + const ngtcp2_ping *fr) { + (void)fr; + + if (outlen < 1) { + return NGTCP2_ERR_NOBUF; + } + + *out++ = NGTCP2_FRAME_PING; + + return 1; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_data_blocked_frame(uint8_t *out, size_t outlen, + const ngtcp2_data_blocked *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->offset); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_DATA_BLOCKED; + p = ngtcp2_put_uvarint(p, fr->offset); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_stream_data_blocked_frame( + uint8_t *out, size_t outlen, const ngtcp2_stream_data_blocked *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) + + ngtcp2_put_uvarintlen(fr->offset); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_STREAM_DATA_BLOCKED; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + p = ngtcp2_put_uvarint(p, fr->offset); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_streams_blocked_frame(uint8_t *out, size_t outlen, + const ngtcp2_streams_blocked *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->max_streams); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = fr->type; + p = ngtcp2_put_uvarint(p, fr->max_streams); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_new_connection_id_frame(uint8_t *out, size_t outlen, + const ngtcp2_new_connection_id *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->seq) + + ngtcp2_put_uvarintlen(fr->retire_prior_to) + 1 + + fr->cid.datalen + NGTCP2_STATELESS_RESET_TOKENLEN; + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_NEW_CONNECTION_ID; + p = ngtcp2_put_uvarint(p, fr->seq); + p = ngtcp2_put_uvarint(p, fr->retire_prior_to); + *p++ = (uint8_t)fr->cid.datalen; + p = ngtcp2_cpymem(p, fr->cid.data, fr->cid.datalen); + p = ngtcp2_cpymem(p, fr->stateless_reset_token, + NGTCP2_STATELESS_RESET_TOKENLEN); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_stop_sending_frame(uint8_t *out, size_t outlen, + const ngtcp2_stop_sending *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen((uint64_t)fr->stream_id) + + ngtcp2_put_uvarintlen(fr->app_error_code); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_STOP_SENDING; + p = ngtcp2_put_uvarint(p, (uint64_t)fr->stream_id); + p = ngtcp2_put_uvarint(p, fr->app_error_code); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_path_challenge_frame(uint8_t *out, size_t outlen, + const ngtcp2_path_challenge *fr) { + size_t len = 1 + 8; + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_PATH_CHALLENGE; + p = ngtcp2_cpymem(p, fr->data, sizeof(fr->data)); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_path_response_frame(uint8_t *out, size_t outlen, + const ngtcp2_path_response *fr) { + size_t len = 1 + 8; + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_PATH_RESPONSE; + p = ngtcp2_cpymem(p, fr->data, sizeof(fr->data)); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_crypto_frame(uint8_t *out, size_t outlen, + const ngtcp2_crypto *fr) { + size_t len = 1; + uint8_t *p; + size_t i; + size_t datalen = 0; + + len += ngtcp2_put_uvarintlen(fr->offset); + + for (i = 0; i < fr->datacnt; ++i) { + datalen += fr->data[i].len; + } + + len += ngtcp2_put_uvarintlen(datalen); + len += datalen; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_CRYPTO; + + p = ngtcp2_put_uvarint(p, fr->offset); + p = ngtcp2_put_uvarint(p, datalen); + + for (i = 0; i < fr->datacnt; ++i) { + assert(fr->data[i].base); + p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen, + const ngtcp2_new_token *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->token.len) + fr->token.len; + uint8_t *p; + + assert(fr->token.len); + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_NEW_TOKEN; + + p = ngtcp2_put_uvarint(p, fr->token.len); + p = ngtcp2_cpymem(p, fr->token.base, fr->token.len); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_encode_retire_connection_id_frame( + uint8_t *out, size_t outlen, const ngtcp2_retire_connection_id *fr) { + size_t len = 1 + ngtcp2_put_uvarintlen(fr->seq); + uint8_t *p; + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = NGTCP2_FRAME_RETIRE_CONNECTION_ID; + + p = ngtcp2_put_uvarint(p, fr->seq); + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize +ngtcp2_pkt_encode_handshake_done_frame(uint8_t *out, size_t outlen, + const ngtcp2_handshake_done *fr) { + (void)fr; + + if (outlen < 1) { + return NGTCP2_ERR_NOBUF; + } + + *out++ = NGTCP2_FRAME_HANDSHAKE_DONE; + + return 1; +} + +ngtcp2_ssize ngtcp2_pkt_encode_datagram_frame(uint8_t *out, size_t outlen, + const ngtcp2_datagram *fr) { + uint64_t datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + uint64_t len = + 1 + + (fr->type == NGTCP2_FRAME_DATAGRAM ? 0 : ngtcp2_put_uvarintlen(datalen)) + + datalen; + uint8_t *p; + size_t i; + + assert(fr->type == NGTCP2_FRAME_DATAGRAM || + fr->type == NGTCP2_FRAME_DATAGRAM_LEN); + + if (outlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = out; + + *p++ = fr->type; + if (fr->type == NGTCP2_FRAME_DATAGRAM_LEN) { + p = ngtcp2_put_uvarint(p, datalen); + } + + for (i = 0; i < fr->datacnt; ++i) { + assert(fr->data[i].len); + assert(fr->data[i].base); + p = ngtcp2_cpymem(p, fr->data[i].base, fr->data[i].len); + } + + assert((size_t)(p - out) == len); + + return (ngtcp2_ssize)len; +} + +ngtcp2_ssize ngtcp2_pkt_write_version_negotiation( + uint8_t *dest, size_t destlen, uint8_t unused_random, const uint8_t *dcid, + size_t dcidlen, const uint8_t *scid, size_t scidlen, const uint32_t *sv, + size_t nsv) { + size_t len = 1 + 4 + 1 + dcidlen + 1 + scidlen + nsv * 4; + uint8_t *p; + size_t i; + + assert(dcidlen < 256); + assert(scidlen < 256); + + if (destlen < len) { + return NGTCP2_ERR_NOBUF; + } + + p = dest; + + *p++ = 0x80 | unused_random; + p = ngtcp2_put_uint32be(p, 0); + *p++ = (uint8_t)dcidlen; + if (dcidlen) { + p = ngtcp2_cpymem(p, dcid, dcidlen); + } + *p++ = (uint8_t)scidlen; + if (scidlen) { + p = ngtcp2_cpymem(p, scid, scidlen); + } + + for (i = 0; i < nsv; ++i) { + p = ngtcp2_put_uint32be(p, sv[i]); + } + + assert((size_t)(p - dest) == len); + + return (ngtcp2_ssize)len; +} + +size_t ngtcp2_pkt_decode_version_negotiation(uint32_t *dest, + const uint8_t *payload, + size_t payloadlen) { + const uint8_t *end = payload + payloadlen; + + assert((payloadlen % sizeof(uint32_t)) == 0); + + for (; payload != end;) { + payload = ngtcp2_get_uint32(dest++, payload); + } + + return payloadlen / sizeof(uint32_t); +} + +int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset *sr, + const uint8_t *payload, + size_t payloadlen) { + const uint8_t *p = payload; + + if (payloadlen < + NGTCP2_MIN_STATELESS_RESET_RANDLEN + NGTCP2_STATELESS_RESET_TOKENLEN) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + sr->rand = p; + sr->randlen = payloadlen - NGTCP2_STATELESS_RESET_TOKENLEN; + p += sr->randlen; + memcpy(sr->stateless_reset_token, p, NGTCP2_STATELESS_RESET_TOKENLEN); + + return 0; +} + +int ngtcp2_pkt_decode_retry(ngtcp2_pkt_retry *dest, const uint8_t *payload, + size_t payloadlen) { + size_t len = /* token */ 1 + NGTCP2_RETRY_TAGLEN; + + if (payloadlen < len) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dest->token.base = (uint8_t *)payload; + dest->token.len = (size_t)(payloadlen - NGTCP2_RETRY_TAGLEN); + ngtcp2_cpymem(dest->tag, payload + dest->token.len, NGTCP2_RETRY_TAGLEN); + + return 0; +} + +int64_t ngtcp2_pkt_adjust_pkt_num(int64_t max_pkt_num, int64_t pkt_num, + size_t n) { + int64_t expected = max_pkt_num + 1; + int64_t win = (int64_t)1 << n; + int64_t hwin = win / 2; + int64_t mask = win - 1; + int64_t cand = (expected & ~mask) | pkt_num; + + if (cand <= expected - hwin) { + assert(cand <= (int64_t)NGTCP2_MAX_VARINT - win); + return cand + win; + } + if (cand > expected + hwin && cand >= win) { + return cand - win; + } + return cand; +} + +int ngtcp2_pkt_validate_ack(ngtcp2_ack *fr) { + int64_t largest_ack = fr->largest_ack; + size_t i; + + if (largest_ack < (int64_t)fr->first_ack_range) { + return NGTCP2_ERR_ACK_FRAME; + } + + largest_ack -= (int64_t)fr->first_ack_range; + + for (i = 0; i < fr->rangecnt; ++i) { + if (largest_ack < (int64_t)fr->ranges[i].gap + 2) { + return NGTCP2_ERR_ACK_FRAME; + } + + largest_ack -= (int64_t)fr->ranges[i].gap + 2; + + if (largest_ack < (int64_t)fr->ranges[i].len) { + return NGTCP2_ERR_ACK_FRAME; + } + + largest_ack -= (int64_t)fr->ranges[i].len; + } + + return 0; +} + +ngtcp2_ssize +ngtcp2_pkt_write_stateless_reset(uint8_t *dest, size_t destlen, + const uint8_t *stateless_reset_token, + const uint8_t *rand, size_t randlen) { + uint8_t *p; + + if (destlen < + NGTCP2_MIN_STATELESS_RESET_RANDLEN + NGTCP2_STATELESS_RESET_TOKENLEN) { + return NGTCP2_ERR_NOBUF; + } + + if (randlen < NGTCP2_MIN_STATELESS_RESET_RANDLEN) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p = dest; + + randlen = ngtcp2_min(destlen - NGTCP2_STATELESS_RESET_TOKENLEN, randlen); + + p = ngtcp2_cpymem(p, rand, randlen); + p = ngtcp2_cpymem(p, stateless_reset_token, NGTCP2_STATELESS_RESET_TOKENLEN); + *dest = (uint8_t)((*dest & 0x7fu) | 0x40u); + + return p - dest; +} + +ngtcp2_ssize ngtcp2_pkt_write_retry( + uint8_t *dest, size_t destlen, uint32_t version, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, const ngtcp2_cid *odcid, const uint8_t *token, + size_t tokenlen, ngtcp2_encrypt encrypt, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx) { + ngtcp2_pkt_hd hd; + uint8_t pseudo_retry[1500]; + ngtcp2_ssize pseudo_retrylen; + uint8_t tag[NGTCP2_RETRY_TAGLEN]; + int rv; + uint8_t *p; + size_t offset; + const uint8_t *nonce; + size_t noncelen; + + assert(tokenlen > 0); + assert(!ngtcp2_cid_eq(scid, odcid)); + + /* Retry packet is sent at most once per one connection attempt. In + the first connection attempt, client has to send random DCID + which is at least NGTCP2_MIN_INITIAL_DCIDLEN bytes long. */ + if (odcid->datalen < NGTCP2_MIN_INITIAL_DCIDLEN) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_RETRY, dcid, + scid, /* pkt_num = */ 0, /* pkt_numlen = */ 1, version, + /* len = */ 0); + + pseudo_retrylen = + ngtcp2_pkt_encode_pseudo_retry(pseudo_retry, sizeof(pseudo_retry), &hd, + /* unused = */ 0, odcid, token, tokenlen); + if (pseudo_retrylen < 0) { + return pseudo_retrylen; + } + + switch (version) { + case NGTCP2_PROTO_VER_V1: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V1; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2_DRAFT: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V2_DRAFT; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V2_DRAFT) - 1; + break; + default: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_DRAFT; + noncelen = sizeof(NGTCP2_RETRY_NONCE_DRAFT) - 1; + } + + /* OpenSSL does not like NULL plaintext. */ + rv = encrypt(tag, aead, aead_ctx, (const uint8_t *)"", 0, nonce, noncelen, + pseudo_retry, (size_t)pseudo_retrylen); + if (rv != 0) { + return rv; + } + + offset = 1 + odcid->datalen; + if (destlen < (size_t)pseudo_retrylen + sizeof(tag) - offset) { + return NGTCP2_ERR_NOBUF; + } + + p = ngtcp2_cpymem(dest, pseudo_retry + offset, + (size_t)pseudo_retrylen - offset); + p = ngtcp2_cpymem(p, tag, sizeof(tag)); + + return p - dest; +} + +ngtcp2_ssize ngtcp2_pkt_encode_pseudo_retry( + uint8_t *dest, size_t destlen, const ngtcp2_pkt_hd *hd, uint8_t unused, + const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen) { + uint8_t *p = dest; + ngtcp2_ssize nwrite; + + if (destlen < 1 + odcid->datalen) { + return NGTCP2_ERR_NOBUF; + } + + *p++ = (uint8_t)odcid->datalen; + p = ngtcp2_cpymem(p, odcid->data, odcid->datalen); + destlen -= (size_t)(p - dest); + + nwrite = ngtcp2_pkt_encode_hd_long(p, destlen, hd); + if (nwrite < 0) { + return nwrite; + } + + if (destlen < (size_t)nwrite + tokenlen) { + return NGTCP2_ERR_NOBUF; + } + + *p &= 0xf0; + *p |= unused; + + p += nwrite; + + p = ngtcp2_cpymem(p, token, tokenlen); + + return p - dest; +} + +int ngtcp2_pkt_verify_retry_tag(uint32_t version, const ngtcp2_pkt_retry *retry, + const uint8_t *pkt, size_t pktlen, + ngtcp2_encrypt encrypt, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx) { + uint8_t pseudo_retry[1500]; + size_t pseudo_retrylen; + uint8_t *p = pseudo_retry; + int rv; + uint8_t tag[NGTCP2_RETRY_TAGLEN]; + const uint8_t *nonce; + size_t noncelen; + + assert(pktlen >= sizeof(retry->tag)); + + if (sizeof(pseudo_retry) < + 1 + retry->odcid.datalen + pktlen - sizeof(retry->tag)) { + return NGTCP2_ERR_PROTO; + } + + *p++ = (uint8_t)retry->odcid.datalen; + p = ngtcp2_cpymem(p, retry->odcid.data, retry->odcid.datalen); + p = ngtcp2_cpymem(p, pkt, pktlen - sizeof(retry->tag)); + + pseudo_retrylen = (size_t)(p - pseudo_retry); + + switch (version) { + case NGTCP2_PROTO_VER_V1: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V1; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V1) - 1; + break; + case NGTCP2_PROTO_VER_V2_DRAFT: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_V2_DRAFT; + noncelen = sizeof(NGTCP2_RETRY_NONCE_V2_DRAFT) - 1; + break; + default: + nonce = (const uint8_t *)NGTCP2_RETRY_NONCE_DRAFT; + noncelen = sizeof(NGTCP2_RETRY_NONCE_DRAFT) - 1; + } + + /* OpenSSL does not like NULL plaintext. */ + rv = encrypt(tag, aead, aead_ctx, (const uint8_t *)"", 0, nonce, noncelen, + pseudo_retry, pseudo_retrylen); + if (rv != 0) { + return rv; + } + + if (0 != memcmp(retry->tag, tag, sizeof(retry->tag))) { + return NGTCP2_ERR_PROTO; + } + + return 0; +} + +size_t ngtcp2_pkt_stream_max_datalen(int64_t stream_id, uint64_t offset, + uint64_t len, size_t left) { + size_t n = 1 /* type */ + ngtcp2_put_uvarintlen((uint64_t)stream_id) + + (offset ? ngtcp2_put_uvarintlen(offset) : 0); + + if (left <= n) { + return (size_t)-1; + } + + left -= n; + + if (left > 8 + 1073741823 && len > 1073741823) { +#if SIZE_MAX > UINT32_MAX + len = ngtcp2_min(len, 4611686018427387903lu); +#endif /* SIZE_MAX > UINT32_MAX */ + return (size_t)ngtcp2_min(len, (uint64_t)(left - 8)); + } + + if (left > 4 + 16383 && len > 16383) { + len = ngtcp2_min(len, 1073741823); + return (size_t)ngtcp2_min(len, (uint64_t)(left - 4)); + } + + if (left > 2 + 63 && len > 63) { + len = ngtcp2_min(len, 16383); + return (size_t)ngtcp2_min(len, (uint64_t)(left - 2)); + } + + len = ngtcp2_min(len, 63); + return (size_t)ngtcp2_min(len, (uint64_t)(left - 1)); +} + +size_t ngtcp2_pkt_crypto_max_datalen(uint64_t offset, size_t len, size_t left) { + size_t n = 1 /* type */ + ngtcp2_put_uvarintlen(offset); + + /* CRYPTO frame must contain nonzero length data. Return -1 if + there is no space to write crypto data. */ + if (left <= n + 1) { + return (size_t)-1; + } + + left -= n; + + if (left > 8 + 1073741823 && len > 1073741823) { +#if SIZE_MAX > UINT32_MAX + len = ngtcp2_min(len, 4611686018427387903lu); +#endif /* SIZE_MAX > UINT32_MAX */ + return ngtcp2_min(len, left - 8); + } + + if (left > 4 + 16383 && len > 16383) { + len = ngtcp2_min(len, 1073741823); + return ngtcp2_min(len, left - 4); + } + + if (left > 2 + 63 && len > 63) { + len = ngtcp2_min(len, 16383); + return ngtcp2_min(len, left - 2); + } + + len = ngtcp2_min(len, 63); + return ngtcp2_min(len, left - 1); +} + +size_t ngtcp2_pkt_datagram_framelen(size_t len) { + return 1 /* type */ + ngtcp2_put_uvarintlen(len) + len; +} + +int ngtcp2_is_supported_version(uint32_t version) { + switch (version) { + case NGTCP2_PROTO_VER_V1: + case NGTCP2_PROTO_VER_V2_DRAFT: + return 1; + default: + return NGTCP2_PROTO_VER_DRAFT_MIN <= version && + version <= NGTCP2_PROTO_VER_DRAFT_MAX; + } +} + +int ngtcp2_is_reserved_version(uint32_t version) { + return (version & NGTCP2_RESERVED_VERSION_MASK) == + NGTCP2_RESERVED_VERSION_MASK; +} + +uint8_t ngtcp2_pkt_get_type_long(uint32_t version, uint8_t c) { + uint8_t pkt_type = (uint8_t)((c & NGTCP2_LONG_TYPE_MASK) >> 4); + + switch (version) { + case NGTCP2_PROTO_VER_V2_DRAFT: + switch (pkt_type) { + case NGTCP2_PKT_TYPE_INITIAL_V2_DRAFT: + return NGTCP2_PKT_INITIAL; + case NGTCP2_PKT_TYPE_0RTT_V2_DRAFT: + return NGTCP2_PKT_0RTT; + case NGTCP2_PKT_TYPE_HANDSHAKE_V2_DRAFT: + return NGTCP2_PKT_HANDSHAKE; + case NGTCP2_PKT_TYPE_RETRY_V2_DRAFT: + return NGTCP2_PKT_RETRY; + default: + return 0; + } + default: + if (!ngtcp2_is_supported_version(version)) { + return 0; + } + + /* QUIC v1 and draft versions share the same numeric packet + types. */ + switch (pkt_type) { + case NGTCP2_PKT_TYPE_INITIAL_V1: + return NGTCP2_PKT_INITIAL; + case NGTCP2_PKT_TYPE_0RTT_V1: + return NGTCP2_PKT_0RTT; + case NGTCP2_PKT_TYPE_HANDSHAKE_V1: + return NGTCP2_PKT_HANDSHAKE; + case NGTCP2_PKT_TYPE_RETRY_V1: + return NGTCP2_PKT_RETRY; + default: + return 0; + } + } +} + +uint8_t ngtcp2_pkt_versioned_type(uint32_t version, uint32_t pkt_type) { + switch (version) { + case NGTCP2_PROTO_VER_V2_DRAFT: + switch (pkt_type) { + case NGTCP2_PKT_INITIAL: + return NGTCP2_PKT_TYPE_INITIAL_V2_DRAFT; + case NGTCP2_PKT_0RTT: + return NGTCP2_PKT_TYPE_0RTT_V2_DRAFT; + case NGTCP2_PKT_HANDSHAKE: + return NGTCP2_PKT_TYPE_HANDSHAKE_V2_DRAFT; + case NGTCP2_PKT_RETRY: + return NGTCP2_PKT_TYPE_RETRY_V2_DRAFT; + default: + ngtcp2_unreachable(); + } + default: + /* Assume that unsupported versions share the numeric long packet + types with QUIC v1 in order to send a packet to elicit Version + Negotiation packet. */ + + /* QUIC v1 and draft versions share the same numeric packet + types. */ + switch (pkt_type) { + case NGTCP2_PKT_INITIAL: + return NGTCP2_PKT_TYPE_INITIAL_V1; + case NGTCP2_PKT_0RTT: + return NGTCP2_PKT_TYPE_0RTT_V1; + case NGTCP2_PKT_HANDSHAKE: + return NGTCP2_PKT_TYPE_HANDSHAKE_V1; + case NGTCP2_PKT_RETRY: + return NGTCP2_PKT_TYPE_RETRY_V1; + default: + ngtcp2_unreachable(); + } + } +} + +int ngtcp2_pkt_verify_reserved_bits(uint8_t c) { + if (c & NGTCP2_HEADER_FORM_BIT) { + return (c & NGTCP2_LONG_RESERVED_BIT_MASK) == 0 ? 0 : NGTCP2_ERR_PROTO; + } + return (c & NGTCP2_SHORT_RESERVED_BIT_MASK) == 0 ? 0 : NGTCP2_ERR_PROTO; +} diff --git a/lib/ngtcp2_pkt.h b/lib/ngtcp2_pkt.h new file mode 100644 index 0000000..46f51c7 --- /dev/null +++ b/lib/ngtcp2_pkt.h @@ -0,0 +1,1233 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_PKT_H +#define NGTCP2_PKT_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* QUIC header macros */ +#define NGTCP2_HEADER_FORM_BIT 0x80 +#define NGTCP2_FIXED_BIT_MASK 0x40 +#define NGTCP2_PKT_NUMLEN_MASK 0x03 + +/* Long header specific macros */ +#define NGTCP2_LONG_TYPE_MASK 0x30 +#define NGTCP2_LONG_RESERVED_BIT_MASK 0x0c + +/* Short header specific macros */ +#define NGTCP2_SHORT_SPIN_BIT_MASK 0x20 +#define NGTCP2_SHORT_RESERVED_BIT_MASK 0x18 +#define NGTCP2_SHORT_KEY_PHASE_BIT 0x04 + +/* NGTCP2_SR_TYPE is a Type field of Stateless Reset. */ +#define NGTCP2_SR_TYPE 0x1f + +/* NGTCP2_MIN_LONG_HEADERLEN is the minimum length of long header. + That is (1|1|TT|RR|PP)<1> + VERSION<4> + DCIL<1> + SCIL<1> + + LENGTH<1> + PKN<1> */ +#define NGTCP2_MIN_LONG_HEADERLEN (1 + 4 + 1 + 1 + 1 + 1) + +#define NGTCP2_STREAM_FIN_BIT 0x01 +#define NGTCP2_STREAM_LEN_BIT 0x02 +#define NGTCP2_STREAM_OFF_BIT 0x04 + +/* NGTCP2_STREAM_OVERHEAD is the maximum number of bytes required + other than payload for STREAM frame. That is from type field to + the beginning of the payload. */ +#define NGTCP2_STREAM_OVERHEAD (1 + 8 + 8 + 8) + +/* NGTCP2_CRYPTO_OVERHEAD is the maximum number of bytes required + other than payload for CRYPTO frame. That is from type field to + the beginning of the payload. */ +#define NGTCP2_CRYPTO_OVERHEAD (1 + 8 + 8) + +/* NGTCP2_DATAGRAM_OVERHEAD is the maximum number of bytes required + other than payload for DATAGRAM frame. That is from type field to + the beginning of the payload. */ +#define NGTCP2_DATAGRAM_OVERHEAD (1 + 8) + +/* NGTCP2_MIN_FRAME_PAYLOADLEN is the minimum frame payload length. */ +#define NGTCP2_MIN_FRAME_PAYLOADLEN 16 + +/* NGTCP2_MAX_SERVER_STREAM_ID_BIDI is the maximum bidirectional + server stream ID. */ +#define NGTCP2_MAX_SERVER_STREAM_ID_BIDI ((int64_t)0x3ffffffffffffffdll) +/* NGTCP2_MAX_CLIENT_STREAM_ID_BIDI is the maximum bidirectional + client stream ID. */ +#define NGTCP2_MAX_CLIENT_STREAM_ID_BIDI ((int64_t)0x3ffffffffffffffcll) +/* NGTCP2_MAX_SERVER_STREAM_ID_UNI is the maximum unidirectional + server stream ID. */ +#define NGTCP2_MAX_SERVER_STREAM_ID_UNI ((int64_t)0x3fffffffffffffffll) +/* NGTCP2_MAX_CLIENT_STREAM_ID_UNI is the maximum unidirectional + client stream ID. */ +#define NGTCP2_MAX_CLIENT_STREAM_ID_UNI ((int64_t)0x3ffffffffffffffell) + +/* NGTCP2_MAX_NUM_ACK_RANGES is the maximum number of Additional ACK + ranges which this library can create, or decode. */ +#define NGTCP2_MAX_ACK_RANGES 32 + +/* NGTCP2_MAX_PKT_NUM is the maximum packet number. */ +#define NGTCP2_MAX_PKT_NUM ((int64_t)((1ll << 62) - 1)) + +/* NGTCP2_MIN_PKT_EXPANDLEN is the minimum packet size expansion in + addition to the minimum DCID length to hide/trigger Stateless + Reset. */ +#define NGTCP2_MIN_PKT_EXPANDLEN 22 + +/* NGTCP2_RETRY_TAGLEN is the length of Retry packet integrity tag. */ +#define NGTCP2_RETRY_TAGLEN 16 + +/* NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE is the maximum UDP payload size + that this library can write. */ +#define NGTCP2_HARD_MAX_UDP_PAYLOAD_SIZE ((1 << 24) - 1) + +/* NGTCP2_PKT_LENGTHLEN is the number of bytes that is occupied by + Length field in Long packet header. */ +#define NGTCP2_PKT_LENGTHLEN 4 + +/* NGTCP2_PKT_TYPE_INITIAL_V1 is Initial long header packet type for + QUIC v1. */ +#define NGTCP2_PKT_TYPE_INITIAL_V1 0x0 +/* NGTCP2_PKT_TYPE_0RTT_V1 is 0RTT long header packet type for QUIC + v1. */ +#define NGTCP2_PKT_TYPE_0RTT_V1 0x1 +/* NGTCP2_PKT_TYPE_HANDSHAKE_V1 is Handshake long header packet type + for QUIC v1. */ +#define NGTCP2_PKT_TYPE_HANDSHAKE_V1 0x2 +/* NGTCP2_PKT_TYPE_RETRY_V1 is Retry long header packet type for QUIC + v1. */ +#define NGTCP2_PKT_TYPE_RETRY_V1 0x3 + +/* NGTCP2_PKT_TYPE_INITIAL_V2_DRAFT is Initial long header packet type + for QUIC v2 draft. */ +#define NGTCP2_PKT_TYPE_INITIAL_V2_DRAFT 0x1 +/* NGTCP2_PKT_TYPE_0RTT_V2_DRAFT is 0RTT long header packet type for + QUIC v2 draft. */ +#define NGTCP2_PKT_TYPE_0RTT_V2_DRAFT 0x2 +/* NGTCP2_PKT_TYPE_HANDSHAKE_V2_DRAFT is Handshake long header packet + type for QUIC v2 draft. */ +#define NGTCP2_PKT_TYPE_HANDSHAKE_V2_DRAFT 0x3 +/* NGTCP2_PKT_TYPE_RETRY_V2_DRAFT is Retry long header packet type for + QUIC v2 draft. */ +#define NGTCP2_PKT_TYPE_RETRY_V2_DRAFT 0x0 + +typedef struct ngtcp2_pkt_retry { + ngtcp2_cid odcid; + ngtcp2_vec token; + uint8_t tag[NGTCP2_RETRY_TAGLEN]; +} ngtcp2_pkt_retry; + +typedef enum { + NGTCP2_FRAME_PADDING = 0x00, + NGTCP2_FRAME_PING = 0x01, + NGTCP2_FRAME_ACK = 0x02, + NGTCP2_FRAME_ACK_ECN = 0x03, + NGTCP2_FRAME_RESET_STREAM = 0x04, + NGTCP2_FRAME_STOP_SENDING = 0x05, + NGTCP2_FRAME_CRYPTO = 0x06, + NGTCP2_FRAME_NEW_TOKEN = 0x07, + NGTCP2_FRAME_STREAM = 0x08, + NGTCP2_FRAME_MAX_DATA = 0x10, + NGTCP2_FRAME_MAX_STREAM_DATA = 0x11, + NGTCP2_FRAME_MAX_STREAMS_BIDI = 0x12, + NGTCP2_FRAME_MAX_STREAMS_UNI = 0x13, + NGTCP2_FRAME_DATA_BLOCKED = 0x14, + NGTCP2_FRAME_STREAM_DATA_BLOCKED = 0x15, + NGTCP2_FRAME_STREAMS_BLOCKED_BIDI = 0x16, + NGTCP2_FRAME_STREAMS_BLOCKED_UNI = 0x17, + NGTCP2_FRAME_NEW_CONNECTION_ID = 0x18, + NGTCP2_FRAME_RETIRE_CONNECTION_ID = 0x19, + NGTCP2_FRAME_PATH_CHALLENGE = 0x1a, + NGTCP2_FRAME_PATH_RESPONSE = 0x1b, + NGTCP2_FRAME_CONNECTION_CLOSE = 0x1c, + NGTCP2_FRAME_CONNECTION_CLOSE_APP = 0x1d, + NGTCP2_FRAME_HANDSHAKE_DONE = 0x1e, + NGTCP2_FRAME_DATAGRAM = 0x30, + NGTCP2_FRAME_DATAGRAM_LEN = 0x31, +} ngtcp2_frame_type; + +typedef struct ngtcp2_stream { + uint8_t type; + /** + * flags of decoded STREAM frame. This gets ignored when encoding + * STREAM frame. + */ + uint8_t flags; + uint8_t fin; + int64_t stream_id; + uint64_t offset; + /* datacnt is the number of elements that data contains. Although + the length of data is 1 in this definition, the library may + allocate extra bytes to hold more elements. */ + size_t datacnt; + /* data is the array of ngtcp2_vec which references data. */ + ngtcp2_vec data[1]; +} ngtcp2_stream; + +typedef struct ngtcp2_ack_range { + uint64_t gap; + uint64_t len; +} ngtcp2_ack_range; + +typedef struct ngtcp2_ack { + uint8_t type; + int64_t largest_ack; + uint64_t ack_delay; + /** + * ack_delay_unscaled is an ack_delay multiplied by + * 2**ack_delay_component * NGTCP2_MICROSECONDS. + */ + ngtcp2_duration ack_delay_unscaled; + struct { + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + } ecn; + uint64_t first_ack_range; + size_t rangecnt; + ngtcp2_ack_range ranges[1]; +} ngtcp2_ack; + +typedef struct ngtcp2_padding { + uint8_t type; + /** + * The length of contiguous PADDING frames. + */ + size_t len; +} ngtcp2_padding; + +typedef struct ngtcp2_reset_stream { + uint8_t type; + int64_t stream_id; + uint64_t app_error_code; + uint64_t final_size; +} ngtcp2_reset_stream; + +typedef struct ngtcp2_connection_close { + uint8_t type; + uint64_t error_code; + uint64_t frame_type; + size_t reasonlen; + uint8_t *reason; +} ngtcp2_connection_close; + +typedef struct ngtcp2_max_data { + uint8_t type; + /** + * max_data is Maximum Data. + */ + uint64_t max_data; +} ngtcp2_max_data; + +typedef struct ngtcp2_max_stream_data { + uint8_t type; + int64_t stream_id; + uint64_t max_stream_data; +} ngtcp2_max_stream_data; + +typedef struct ngtcp2_max_streams { + uint8_t type; + uint64_t max_streams; +} ngtcp2_max_streams; + +typedef struct ngtcp2_ping { + uint8_t type; +} ngtcp2_ping; + +typedef struct ngtcp2_data_blocked { + uint8_t type; + uint64_t offset; +} ngtcp2_data_blocked; + +typedef struct ngtcp2_stream_data_blocked { + uint8_t type; + int64_t stream_id; + uint64_t offset; +} ngtcp2_stream_data_blocked; + +typedef struct ngtcp2_streams_blocked { + uint8_t type; + uint64_t max_streams; +} ngtcp2_streams_blocked; + +typedef struct ngtcp2_new_connection_id { + uint8_t type; + uint64_t seq; + uint64_t retire_prior_to; + ngtcp2_cid cid; + uint8_t stateless_reset_token[NGTCP2_STATELESS_RESET_TOKENLEN]; +} ngtcp2_new_connection_id; + +typedef struct ngtcp2_stop_sending { + uint8_t type; + int64_t stream_id; + uint64_t app_error_code; +} ngtcp2_stop_sending; + +typedef struct ngtcp2_path_challenge { + uint8_t type; + uint8_t data[NGTCP2_PATH_CHALLENGE_DATALEN]; +} ngtcp2_path_challenge; + +typedef struct ngtcp2_path_response { + uint8_t type; + uint8_t data[NGTCP2_PATH_CHALLENGE_DATALEN]; +} ngtcp2_path_response; + +typedef struct ngtcp2_crypto { + uint8_t type; + uint64_t offset; + /* datacnt is the number of elements that data contains. Although + the length of data is 1 in this definition, the library may + allocate extra bytes to hold more elements. */ + size_t datacnt; + /* data is the array of ngtcp2_vec which references data. */ + ngtcp2_vec data[1]; +} ngtcp2_crypto; + +typedef struct ngtcp2_new_token { + uint8_t type; + ngtcp2_vec token; +} ngtcp2_new_token; + +typedef struct ngtcp2_retire_connection_id { + uint8_t type; + uint64_t seq; +} ngtcp2_retire_connection_id; + +typedef struct ngtcp2_handshake_done { + uint8_t type; +} ngtcp2_handshake_done; + +typedef struct ngtcp2_datagram { + uint8_t type; + /* dgram_id is an opaque identifier chosen by an application. */ + uint64_t dgram_id; + /* datacnt is the number of elements that data contains. */ + size_t datacnt; + /* data is a pointer to ngtcp2_vec array that stores data. */ + ngtcp2_vec *data; + /* rdata is conveniently embedded to ngtcp2_datagram, so that data + field can just point to the address of this field to store a + single vector which is the case when DATAGRAM is received from a + remote endpoint. */ + ngtcp2_vec rdata[1]; +} ngtcp2_datagram; + +typedef union ngtcp2_frame { + uint8_t type; + ngtcp2_stream stream; + ngtcp2_ack ack; + ngtcp2_padding padding; + ngtcp2_reset_stream reset_stream; + ngtcp2_connection_close connection_close; + ngtcp2_max_data max_data; + ngtcp2_max_stream_data max_stream_data; + ngtcp2_max_streams max_streams; + ngtcp2_ping ping; + ngtcp2_data_blocked data_blocked; + ngtcp2_stream_data_blocked stream_data_blocked; + ngtcp2_streams_blocked streams_blocked; + ngtcp2_new_connection_id new_connection_id; + ngtcp2_stop_sending stop_sending; + ngtcp2_path_challenge path_challenge; + ngtcp2_path_response path_response; + ngtcp2_crypto crypto; + ngtcp2_new_token new_token; + ngtcp2_retire_connection_id retire_connection_id; + ngtcp2_handshake_done handshake_done; + ngtcp2_datagram datagram; +} ngtcp2_frame; + +typedef struct ngtcp2_pkt_chain ngtcp2_pkt_chain; + +/* + * ngtcp2_pkt_chain is the chain of incoming packets buffered. + */ +struct ngtcp2_pkt_chain { + ngtcp2_path_storage path; + ngtcp2_pkt_info pi; + ngtcp2_pkt_chain *next; + uint8_t *pkt; + /* pktlen is length of a QUIC packet. */ + size_t pktlen; + /* dgramlen is length of UDP datagram that a QUIC packet is + included. */ + size_t dgramlen; + ngtcp2_tstamp ts; +}; + +/* + * ngtcp2_pkt_chain_new allocates ngtcp2_pkt_chain objects, and + * assigns its pointer to |*ppc|. The content of buffer pointed by + * |pkt| of length |pktlen| is copied into |*ppc|. The packet is + * obtained via the network |path|. The values of path->local and + * path->remote are copied into |*ppc|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_pkt_chain_new(ngtcp2_pkt_chain **ppc, const ngtcp2_path *path, + const ngtcp2_pkt_info *pi, const uint8_t *pkt, + size_t pktlen, size_t dgramlen, ngtcp2_tstamp ts, + const ngtcp2_mem *mem); + +/* + * ngtcp2_pkt_chain_del deallocates |pc|. It also frees the memory + * pointed by |pc|. + */ +void ngtcp2_pkt_chain_del(ngtcp2_pkt_chain *pc, const ngtcp2_mem *mem); + +/* + * ngtcp2_pkt_hd_init initializes |hd| with the given values. If + * |dcid| and/or |scid| is NULL, DCID and SCID of |hd| is empty + * respectively. |pkt_numlen| is the number of bytes used to encode + * |pkt_num| and either 1, 2, or 4. |version| is QUIC version for + * long header. |len| is the length field of Initial, 0RTT, and + * Handshake packets. + */ +void ngtcp2_pkt_hd_init(ngtcp2_pkt_hd *hd, uint8_t flags, uint8_t type, + const ngtcp2_cid *dcid, const ngtcp2_cid *scid, + int64_t pkt_num, size_t pkt_numlen, uint32_t version, + size_t len); + +/* + * ngtcp2_pkt_encode_hd_long encodes |hd| as QUIC long header into + * |out| which has length |outlen|. It returns the number of bytes + * written into |outlen| if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer is too short + */ +ngtcp2_ssize ngtcp2_pkt_encode_hd_long(uint8_t *out, size_t outlen, + const ngtcp2_pkt_hd *hd); + +/* + * ngtcp2_pkt_encode_hd_short encodes |hd| as QUIC short header into + * |out| which has length |outlen|. It returns the number of bytes + * written into |outlen| if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer is too short + */ +ngtcp2_ssize ngtcp2_pkt_encode_hd_short(uint8_t *out, size_t outlen, + const ngtcp2_pkt_hd *hd); + +/** + * @function + * + * `ngtcp2_pkt_decode_frame` decodes a QUIC frame from the buffer + * pointed by |payload| whose length is |payloadlen|. + * + * This function returns the number of bytes read to decode a single + * frame if it succeeds, or one of the following negative error codes: + * + * :enum:`NGTCP2_ERR_FRAME_ENCODING` + * Frame is badly formatted; or frame type is unknown; or + * |payloadlen| is 0. + */ +ngtcp2_ssize ngtcp2_pkt_decode_frame(ngtcp2_frame *dest, const uint8_t *payload, + size_t payloadlen); + +/** + * @function + * + * `ngtcp2_pkt_encode_frame` encodes a frame |fm| into the buffer + * pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written to the buffer, or + * one of the following negative error codes: + * + * :enum:`NGTCP2_ERR_NOBUF` + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_frame(uint8_t *out, size_t outlen, + ngtcp2_frame *fr); + +/* + * ngtcp2_pkt_decode_version_negotiation decodes Version Negotiation + * packet payload |payload| of length |payloadlen|, and stores the + * result in |dest|. |dest| must have enough capacity to store the + * result. |payloadlen| also must be a multiple of sizeof(uint32_t). + * + * This function returns the number of versions written in |dest|. + */ +size_t ngtcp2_pkt_decode_version_negotiation(uint32_t *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_stateless_reset decodes Stateless Reset payload + * |payload| of length |payloadlen|. The |payload| must start with + * Stateless Reset Token. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Payloadlen is too short. + */ +int ngtcp2_pkt_decode_stateless_reset(ngtcp2_pkt_stateless_reset *sr, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_retry decodes Retry packet payload |payload| of + * length |payloadlen|. The |payload| must start with Retry token + * field. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_INVALID_ARGUMENT + * Payloadlen is too short. + */ +int ngtcp2_pkt_decode_retry(ngtcp2_pkt_retry *dest, const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_stream_frame decodes STREAM frame from |payload| + * of length |payloadlen|. The result is stored in the object pointed + * by |dest|. STREAM frame must start at payload[0]. This function + * finishes when it decodes one STREAM frame, and returns the exact + * number of bytes read to decode a frame if it succeeds, or one of + * the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include STREAM frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_stream_frame(ngtcp2_stream *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_ack_frame decodes ACK frame from |payload| of + * length |payloadlen|. The result is stored in the object pointed by + * |dest|. ACK frame must start at payload[0]. This function + * finishes when it decodes one ACK frame, and returns the exact + * number of bytes read to decode a frame if it succeeds, or one of + * the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include ACK frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_ack_frame(ngtcp2_ack *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_padding_frame decodes contiguous PADDING frames + * from |payload| of length |payloadlen|. It continues to parse + * frames as long as the frame type is PADDING. This finishes when it + * encounters the frame type which is not PADDING, or all input data + * is read. The first byte (payload[0]) must be NGTCP2_FRAME_PADDING. + * This function returns the exact number of bytes read to decode + * PADDING frames. + */ +ngtcp2_ssize ngtcp2_pkt_decode_padding_frame(ngtcp2_padding *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_reset_stream_frame decodes RESET_STREAM frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. RESET_STREAM frame must start at + * payload[0]. This function finishes when it decodes one + * RESET_STREAM frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include RESET_STREAM frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_reset_stream_frame(ngtcp2_reset_stream *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_connection_close_frame decodes CONNECTION_CLOSE + * frame from |payload| of length |payloadlen|. The result is stored + * in the object pointed by |dest|. CONNECTION_CLOSE frame must start + * at payload[0]. This function finishes it decodes one + * CONNECTION_CLOSE frame, and returns the exact number of bytes read + * to decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include CONNECTION_CLOSE frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_connection_close_frame( + ngtcp2_connection_close *dest, const uint8_t *payload, size_t payloadlen); + +/* + * ngtcp2_pkt_decode_max_data_frame decodes MAX_DATA frame from + * |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. MAX_DATA frame must start at payload[0]. + * This function finishes when it decodes one MAX_DATA frame, and + * returns the exact number of bytes read to decode a frame if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include MAX_DATA frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_max_data_frame(ngtcp2_max_data *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_max_stream_data_frame decodes MAX_STREAM_DATA + * frame from |payload| of length |payloadlen|. The result is stored + * in the object pointed by |dest|. MAX_STREAM_DATA frame must start + * at payload[0]. This function finishes when it decodes one + * MAX_STREAM_DATA frame, and returns the exact number of bytes read + * to decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include MAX_STREAM_DATA frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_max_stream_data_frame( + ngtcp2_max_stream_data *dest, const uint8_t *payload, size_t payloadlen); + +/* + * ngtcp2_pkt_decode_max_streams_frame decodes MAX_STREAMS frame from + * |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. MAX_STREAMS frame must start at + * payload[0]. This function finishes when it decodes one MAX_STREAMS + * frame, and returns the exact number of bytes read to decode a frame + * if it succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include MAX_STREAMS frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_max_streams_frame(ngtcp2_max_streams *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_ping_frame decodes PING frame from |payload| of + * length |payloadlen|. The result is stored in the object pointed by + * |dest|. PING frame must start at payload[0]. This function + * finishes when it decodes one PING frame, and returns the exact + * number of bytes read to decode a frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_ping_frame(ngtcp2_ping *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_data_blocked_frame decodes DATA_BLOCKED frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. DATA_BLOCKED frame must start at + * payload[0]. This function finishes when it decodes one + * DATA_BLOCKED frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include DATA_BLOCKED frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_data_blocked_frame(ngtcp2_data_blocked *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_stream_data_blocked_frame decodes + * STREAM_DATA_BLOCKED frame from |payload| of length |payloadlen|. + * The result is stored in the object pointed by |dest|. + * STREAM_DATA_BLOCKED frame must start at payload[0]. This function + * finishes when it decodes one STREAM_DATA_BLOCKED frame, and returns + * the exact number of bytes read to decode a frame if it succeeds, or + * one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include STREAM_DATA_BLOCKED frame. + */ +ngtcp2_ssize +ngtcp2_pkt_decode_stream_data_blocked_frame(ngtcp2_stream_data_blocked *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_streams_blocked_frame decodes STREAMS_BLOCKED + * frame from |payload| of length |payloadlen|. The result is stored + * in the object pointed by |dest|. STREAMS_BLOCKED frame must start + * at payload[0]. This function finishes when it decodes one + * STREAMS_BLOCKED frame, and returns the exact number of bytes read + * to decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include STREAMS_BLOCKED frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_streams_blocked_frame( + ngtcp2_streams_blocked *dest, const uint8_t *payload, size_t payloadlen); + +/* + * ngtcp2_pkt_decode_new_connection_id_frame decodes NEW_CONNECTION_ID + * frame from |payload| of length |payloadlen|. The result is stored + * in the object pointed by |dest|. NEW_CONNECTION_ID frame must + * start at payload[0]. This function finishes when it decodes one + * NEW_CONNECTION_ID frame, and returns the exact number of bytes read + * to decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include NEW_CONNECTION_ID frame; or the + * length of CID is strictly less than NGTCP2_MIN_CIDLEN or + * greater than NGTCP2_MAX_CIDLEN. + */ +ngtcp2_ssize ngtcp2_pkt_decode_new_connection_id_frame( + ngtcp2_new_connection_id *dest, const uint8_t *payload, size_t payloadlen); + +/* + * ngtcp2_pkt_decode_stop_sending_frame decodes STOP_SENDING frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. STOP_SENDING frame must start at + * payload[0]. This function finishes when it decodes one + * STOP_SENDING frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include STOP_SENDING frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_stop_sending_frame(ngtcp2_stop_sending *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_path_challenge_frame decodes PATH_CHALLENGE frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. PATH_CHALLENGE frame must start at + * payload[0]. This function finishes when it decodes one + * PATH_CHALLENGE frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include PATH_CHALLENGE frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_path_challenge_frame(ngtcp2_path_challenge *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_path_response_frame decodes PATH_RESPONSE frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. PATH_RESPONSE frame must start at + * payload[0]. This function finishes when it decodes one + * PATH_RESPONSE frame, and returns the exact number of bytes read to + * decode a frame if it succeeds, or one of the following negative + * error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include PATH_RESPONSE frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_path_response_frame(ngtcp2_path_response *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_crypto_frame decodes CRYPTO frame from |payload| + * of length |payloadlen|. The result is stored in the object pointed + * by |dest|. CRYPTO frame must start at payload[0]. This function + * finishes when it decodes one CRYPTO frame, and returns the exact + * number of bytes read to decode a frame if it succeeds, or one of + * the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include CRYPTO frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_crypto_frame(ngtcp2_crypto *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_new_token_frame decodes NEW_TOKEN frame from + * |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. NEW_TOKEN frame must start at + * payload[0]. This function finishes when it decodes one NEW_TOKEN + * frame, and returns the exact number of bytes read to decode a frame + * if it succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include NEW_TOKEN frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_new_token_frame(ngtcp2_new_token *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_retire_connection_id_frame decodes RETIRE_CONNECTION_ID + * frame from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. RETIRE_CONNECTION_ID frame must start at + * payload[0]. This function finishes when it decodes one RETIRE_CONNECTION_ID + * frame, and returns the exact number of bytes read to decode a frame + * if it succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include RETIRE_CONNECTION_ID frame. + */ +ngtcp2_ssize +ngtcp2_pkt_decode_retire_connection_id_frame(ngtcp2_retire_connection_id *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_handshake_done_frame decodes HANDSHAKE_DONE frame + * from |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. HANDSHAKE_DONE frame must start at + * payload[0]. This function finishes when it decodes one + * HANDSHAKE_DONE frame, and returns the exact number of bytes read to + * decode a frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_handshake_done_frame(ngtcp2_handshake_done *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_decode_datagram_frame decodes DATAGRAM frame from + * |payload| of length |payloadlen|. The result is stored in the + * object pointed by |dest|. DATAGRAM frame must start at payload[0]. + * This function finishes when it decodes one DATAGRAM frame, and + * returns the exact number of bytes read to decode a frame if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_FRAME_ENCODING + * Payload is too short to include DATAGRAM frame. + */ +ngtcp2_ssize ngtcp2_pkt_decode_datagram_frame(ngtcp2_datagram *dest, + const uint8_t *payload, + size_t payloadlen); + +/* + * ngtcp2_pkt_encode_stream_frame encodes STREAM frame |fr| into the + * buffer pointed by |out| of length |outlen|. + * + * This function assigns <the serialized frame type> & + * ~NGTCP2_FRAME_STREAM to fr->flags. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_stream_frame(uint8_t *out, size_t outlen, + ngtcp2_stream *fr); + +/* + * ngtcp2_pkt_encode_ack_frame encodes ACK frame |fr| into the buffer + * pointed by |out| of length |outlen|. + * + * This function assigns <the serialized frame type> & + * ~NGTCP2_FRAME_ACK to fr->flags. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_ack_frame(uint8_t *out, size_t outlen, + ngtcp2_ack *fr); + +/* + * ngtcp2_pkt_encode_padding_frame encodes PADDING frame |fr| into the + * buffer pointed by |out| of length |outlen|. + * + * This function encodes consecutive fr->len PADDING frames. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write frame(s). + */ +ngtcp2_ssize ngtcp2_pkt_encode_padding_frame(uint8_t *out, size_t outlen, + const ngtcp2_padding *fr); + +/* + * ngtcp2_pkt_encode_reset_stream_frame encodes RESET_STREAM frame + * |fr| into the buffer pointed by |out| of length |buflen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_reset_stream_frame(uint8_t *out, size_t outlen, + const ngtcp2_reset_stream *fr); + +/* + * ngtcp2_pkt_encode_connection_close_frame encodes CONNECTION_CLOSE + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_connection_close_frame(uint8_t *out, size_t outlen, + const ngtcp2_connection_close *fr); + +/* + * ngtcp2_pkt_encode_max_data_frame encodes MAX_DATA frame |fr| into + * the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_max_data_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_data *fr); + +/* + * ngtcp2_pkt_encode_max_stream_data_frame encodes MAX_STREAM_DATA + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_max_stream_data_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_stream_data *fr); + +/* + * ngtcp2_pkt_encode_max_streams_frame encodes MAX_STREAMS + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_max_streams_frame(uint8_t *out, size_t outlen, + const ngtcp2_max_streams *fr); + +/* + * ngtcp2_pkt_encode_ping_frame encodes PING frame |fr| into the + * buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_ping_frame(uint8_t *out, size_t outlen, + const ngtcp2_ping *fr); + +/* + * ngtcp2_pkt_encode_data_blocked_frame encodes DATA_BLOCKED frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_data_blocked_frame(uint8_t *out, size_t outlen, + const ngtcp2_data_blocked *fr); + +/* + * ngtcp2_pkt_encode_stream_data_blocked_frame encodes + * STREAM_DATA_BLOCKED frame |fr| into the buffer pointed by |out| of + * length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_stream_data_blocked_frame( + uint8_t *out, size_t outlen, const ngtcp2_stream_data_blocked *fr); + +/* + * ngtcp2_pkt_encode_streams_blocked_frame encodes STREAMS_BLOCKED + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_streams_blocked_frame(uint8_t *out, size_t outlen, + const ngtcp2_streams_blocked *fr); + +/* + * ngtcp2_pkt_encode_new_connection_id_frame encodes NEW_CONNECTION_ID + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_new_connection_id_frame(uint8_t *out, size_t outlen, + const ngtcp2_new_connection_id *fr); + +/* + * ngtcp2_pkt_encode_stop_sending_frame encodes STOP_SENDING frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_stop_sending_frame(uint8_t *out, size_t outlen, + const ngtcp2_stop_sending *fr); + +/* + * ngtcp2_pkt_encode_path_challenge_frame encodes PATH_CHALLENGE frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_path_challenge_frame(uint8_t *out, size_t outlen, + const ngtcp2_path_challenge *fr); + +/* + * ngtcp2_pkt_encode_path_response_frame encodes PATH_RESPONSE frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_path_response_frame(uint8_t *out, size_t outlen, + const ngtcp2_path_response *fr); + +/* + * ngtcp2_pkt_encode_crypto_frame encodes CRYPTO frame |fr| into the + * buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_crypto_frame(uint8_t *out, size_t outlen, + const ngtcp2_crypto *fr); + +/* + * ngtcp2_pkt_encode_new_token_frame encodes NEW_TOKEN frame |fr| into + * the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen, + const ngtcp2_new_token *fr); + +/* + * ngtcp2_pkt_encode_retire_connection_id_frame encodes RETIRE_CONNECTION_ID + * frame |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_retire_connection_id_frame( + uint8_t *out, size_t outlen, const ngtcp2_retire_connection_id *fr); + +/* + * ngtcp2_pkt_encode_handshake_done_frame encodes HANDSHAKE_DONE frame + * |fr| into the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize +ngtcp2_pkt_encode_handshake_done_frame(uint8_t *out, size_t outlen, + const ngtcp2_handshake_done *fr); + +/* + * ngtcp2_pkt_encode_datagram_frame encodes DATAGRAM frame |fr| into + * the buffer pointed by |out| of length |outlen|. + * + * This function returns the number of bytes written if it succeeds, + * or one of the following negative error codes: + * + * NGTCP2_ERR_NOBUF + * Buffer does not have enough capacity to write a frame. + */ +ngtcp2_ssize ngtcp2_pkt_encode_datagram_frame(uint8_t *out, size_t outlen, + const ngtcp2_datagram *fr); + +/* + * ngtcp2_pkt_adjust_pkt_num find the full 64 bits packet number for + * |pkt_num|, which is expected to be least significant |n| bits. The + * |max_pkt_num| is the highest successfully authenticated packet + * number. + */ +int64_t ngtcp2_pkt_adjust_pkt_num(int64_t max_pkt_num, int64_t pkt_num, + size_t n); + +/* + * ngtcp2_pkt_validate_ack checks that ack is malformed or not. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_ACK_FRAME + * ACK frame is malformed + */ +int ngtcp2_pkt_validate_ack(ngtcp2_ack *fr); + +/* + * ngtcp2_pkt_stream_max_datalen returns the maximum number of bytes + * which can be sent for stream denoted by |stream_id|. |offset| is + * an offset of within the stream. |len| is the estimated number of + * bytes to be sent. |left| is the size of buffer. If |left| is too + * small to write STREAM frame, this function returns (size_t)-1. + */ +size_t ngtcp2_pkt_stream_max_datalen(int64_t stream_id, uint64_t offset, + uint64_t len, size_t left); + +/* + * ngtcp2_pkt_crypto_max_datalen returns the maximum number of bytes + * which can be sent for crypto stream. |offset| is an offset of + * within the crypto stream. |len| is the estimated number of bytes + * to be sent. |left| is the size of buffer. If |left| is too small + * to write CRYPTO frame, this function returns (size_t)-1. + */ +size_t ngtcp2_pkt_crypto_max_datalen(uint64_t offset, size_t len, size_t left); + +/* + * ngtcp2_pkt_datagram_framelen returns the length of DATAGRAM frame + * to encode |len| bytes of data. + */ +size_t ngtcp2_pkt_datagram_framelen(size_t len); + +/* + * ngtcp2_pkt_verify_reserved_bits verifies that the first byte |c| of + * the packet header has the correct reserved bits. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * Reserved bits has wrong value. + */ +int ngtcp2_pkt_verify_reserved_bits(uint8_t c); + +/* + * ngtcp2_pkt_encode_pseudo_retry encodes Retry pseudo-packet in the + * buffer pointed by |dest| of length |destlen|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_BUF + * Buffer is too short. + */ +ngtcp2_ssize ngtcp2_pkt_encode_pseudo_retry( + uint8_t *dest, size_t destlen, const ngtcp2_pkt_hd *hd, uint8_t unused, + const ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen); + +/* + * ngtcp2_pkt_verify_retry_tag verifies Retry packet. The buffer + * pointed by |pkt| of length |pktlen| must contain Retry packet + * including packet header. The odcid and tag fields of |retry| must + * be specified. |aead| must be AEAD_AES_128_GCM. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PROTO + * Verification failed. + */ +int ngtcp2_pkt_verify_retry_tag(uint32_t version, const ngtcp2_pkt_retry *retry, + const uint8_t *pkt, size_t pktlen, + ngtcp2_encrypt encrypt, + const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx); + +/* + * ngtcp2_pkt_versioned_type returns versioned packet type for a + * version |version| that corresponds to the version-independent + * |pkt_type|. + */ +uint8_t ngtcp2_pkt_versioned_type(uint32_t version, uint32_t pkt_type); + +/** + * @function + * + * `ngtcp2_pkt_get_type_long` returns the version-independent long + * packet type. |version| is the QUIC version. |c| is the first byte + * of Long packet header. If |version| is not supported by the + * library, it returns 0. + */ +uint8_t ngtcp2_pkt_get_type_long(uint32_t version, uint8_t c); + +#endif /* NGTCP2_PKT_H */ diff --git a/lib/ngtcp2_pmtud.c b/lib/ngtcp2_pmtud.c new file mode 100644 index 0000000..771ef5e --- /dev/null +++ b/lib/ngtcp2_pmtud.c @@ -0,0 +1,160 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_pmtud.h" + +#include <assert.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_macro.h" + +/* NGTCP2_PMTUD_PROBE_NUM_MAX is the maximum number of packets sent + for each probe. */ +#define NGTCP2_PMTUD_PROBE_NUM_MAX 3 + +static size_t mtu_probes[] = { + 1454 - 48, /* The well known MTU used by a domestic optic fiber + service in Japan. */ + 1390 - 48, /* Typical Tunneled MTU */ + 1280 - 48, /* IPv6 minimum MTU */ + 1492 - 48, /* PPPoE */ +}; + +#define NGTCP2_MTU_PROBESLEN ngtcp2_arraylen(mtu_probes) + +int ngtcp2_pmtud_new(ngtcp2_pmtud **ppmtud, size_t max_udp_payload_size, + size_t hard_max_udp_payload_size, int64_t tx_pkt_num, + const ngtcp2_mem *mem) { + ngtcp2_pmtud *pmtud = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pmtud)); + + if (pmtud == NULL) { + return NGTCP2_ERR_NOMEM; + } + + pmtud->mem = mem; + pmtud->mtu_idx = 0; + pmtud->num_pkts_sent = 0; + pmtud->expiry = UINT64_MAX; + pmtud->tx_pkt_num = tx_pkt_num; + pmtud->max_udp_payload_size = max_udp_payload_size; + pmtud->hard_max_udp_payload_size = hard_max_udp_payload_size; + pmtud->min_fail_udp_payload_size = SIZE_MAX; + + for (; pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN; ++pmtud->mtu_idx) { + if (mtu_probes[pmtud->mtu_idx] > pmtud->hard_max_udp_payload_size) { + continue; + } + if (mtu_probes[pmtud->mtu_idx] > pmtud->max_udp_payload_size) { + break; + } + } + + *ppmtud = pmtud; + + return 0; +} + +void ngtcp2_pmtud_del(ngtcp2_pmtud *pmtud) { + if (!pmtud) { + return; + } + + ngtcp2_mem_free(pmtud->mem, pmtud); +} + +size_t ngtcp2_pmtud_probelen(ngtcp2_pmtud *pmtud) { + assert(pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN); + + return mtu_probes[pmtud->mtu_idx]; +} + +void ngtcp2_pmtud_probe_sent(ngtcp2_pmtud *pmtud, ngtcp2_duration pto, + ngtcp2_tstamp ts) { + ngtcp2_tstamp timeout; + + if (++pmtud->num_pkts_sent < NGTCP2_PMTUD_PROBE_NUM_MAX) { + timeout = pto; + } else { + timeout = 3 * pto; + } + + pmtud->expiry = ts + timeout; +} + +int ngtcp2_pmtud_require_probe(ngtcp2_pmtud *pmtud) { + return pmtud->expiry == UINT64_MAX; +} + +static void pmtud_next_probe(ngtcp2_pmtud *pmtud) { + assert(pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN); + + ++pmtud->mtu_idx; + pmtud->num_pkts_sent = 0; + pmtud->expiry = UINT64_MAX; + + for (; pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN; ++pmtud->mtu_idx) { + if (mtu_probes[pmtud->mtu_idx] <= pmtud->max_udp_payload_size || + mtu_probes[pmtud->mtu_idx] > pmtud->hard_max_udp_payload_size) { + continue; + } + + if (mtu_probes[pmtud->mtu_idx] < pmtud->min_fail_udp_payload_size) { + break; + } + } +} + +void ngtcp2_pmtud_probe_success(ngtcp2_pmtud *pmtud, size_t payloadlen) { + pmtud->max_udp_payload_size = + ngtcp2_max(pmtud->max_udp_payload_size, payloadlen); + + assert(pmtud->mtu_idx < NGTCP2_MTU_PROBESLEN); + + if (mtu_probes[pmtud->mtu_idx] > pmtud->max_udp_payload_size) { + return; + } + + pmtud_next_probe(pmtud); +} + +void ngtcp2_pmtud_handle_expiry(ngtcp2_pmtud *pmtud, ngtcp2_tstamp ts) { + if (ts < pmtud->expiry) { + return; + } + + pmtud->expiry = UINT64_MAX; + + if (pmtud->num_pkts_sent < NGTCP2_PMTUD_PROBE_NUM_MAX) { + return; + } + + pmtud->min_fail_udp_payload_size = + ngtcp2_min(pmtud->min_fail_udp_payload_size, mtu_probes[pmtud->mtu_idx]); + + pmtud_next_probe(pmtud); +} + +int ngtcp2_pmtud_finished(ngtcp2_pmtud *pmtud) { + return pmtud->mtu_idx >= NGTCP2_MTU_PROBESLEN; +} diff --git a/lib/ngtcp2_pmtud.h b/lib/ngtcp2_pmtud.h new file mode 100644 index 0000000..6b2e691 --- /dev/null +++ b/lib/ngtcp2_pmtud.h @@ -0,0 +1,123 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_PMTUD_H +#define NGTCP2_PMTUD_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +typedef struct ngtcp2_pmtud { + const ngtcp2_mem *mem; + /* mtu_idx is the index of UDP payload size candidates to try + out. */ + size_t mtu_idx; + /* num_pkts_sent is the number of mtu_idx sized UDP datagram payload + sent */ + size_t num_pkts_sent; + /* expiry is the expired, if it is reached, send out the next UDP + datagram. UINT64_MAX means no expiry, or expiration is canceled. + In either case, new probe packet should be sent unless we have + done all attempts. */ + ngtcp2_tstamp expiry; + /* tx_pkt_num is the smallest outgoing packet number where the + current discovery is performed. In other words, acknowledging + packet whose packet number lower than that does not indicate the + success of Path MTU Discovery. */ + int64_t tx_pkt_num; + /* max_udp_payload_size is the maximum UDP payload size which is + known to work. */ + size_t max_udp_payload_size; + /* hard_max_udp_payload_size is the maximum UDP payload size that is + going to be probed. */ + size_t hard_max_udp_payload_size; + /* min_fail_udp_payload_size is the minimum UDP payload size that is + known to fail. */ + size_t min_fail_udp_payload_size; +} ngtcp2_pmtud; + +/* + * ngtcp2_pmtud_new creates new ngtcp2_pmtud object, and assigns its + * pointer to |*ppmtud|. |max_udp_payload_size| is the maximum UDP + * payload size that is known to work for the current path. + * |tx_pkt_num| should be the next packet number to send, which is + * used to differentiate the PMTUD probe packet sent by the previous + * PMTUD. PMTUD might finish immediately if |max_udp_payload_size| is + * larger than or equal to all UDP payload probe candidates. + * Therefore, call ngtcp2_pmtud_finished to check this situation. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_pmtud_new(ngtcp2_pmtud **ppmtud, size_t max_udp_payload_size, + size_t hard_max_udp_payload_size, int64_t tx_pkt_num, + const ngtcp2_mem *mem); + +/* + * ngtcp2_pmtud_del deletes |pmtud|. + */ +void ngtcp2_pmtud_del(ngtcp2_pmtud *pmtud); + +/* + * ngtcp2_pmtud_probelen returns the length of UDP payload size for a + * PMTUD probe packet. + */ +size_t ngtcp2_pmtud_probelen(ngtcp2_pmtud *pmtud); + +/* + * ngtcp2_pmtud_probe_sent should be invoked when a PMTUD probe packet is + * sent. + */ +void ngtcp2_pmtud_probe_sent(ngtcp2_pmtud *pmtud, ngtcp2_duration pto, + ngtcp2_tstamp ts); + +/* + * ngtcp2_pmtud_require_probe returns nonzero if a PMTUD probe packet + * should be sent. + */ +int ngtcp2_pmtud_require_probe(ngtcp2_pmtud *pmtud); + +/* + * ngtcp2_pmtud_probe_success should be invoked when a PMTUD probe + * UDP datagram sized |payloadlen| is acknowledged. + */ +void ngtcp2_pmtud_probe_success(ngtcp2_pmtud *pmtud, size_t payloadlen); + +/* + * ngtcp2_pmtud_handle_expiry handles expiry. + */ +void ngtcp2_pmtud_handle_expiry(ngtcp2_pmtud *pmtud, ngtcp2_tstamp ts); + +/* + * ngtcp2_pmtud_finished returns nonzero if PMTUD has finished. + */ +int ngtcp2_pmtud_finished(ngtcp2_pmtud *pmtud); + +#endif /* NGTCP2_PMTUD_H */ diff --git a/lib/ngtcp2_ppe.c b/lib/ngtcp2_ppe.c new file mode 100644 index 0000000..82aa80c --- /dev/null +++ b/lib/ngtcp2_ppe.c @@ -0,0 +1,230 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_ppe.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_conv.h" + +void ngtcp2_ppe_init(ngtcp2_ppe *ppe, uint8_t *out, size_t outlen, + ngtcp2_crypto_cc *cc) { + ngtcp2_buf_init(&ppe->buf, out, outlen); + + ppe->hdlen = 0; + ppe->len_offset = 0; + ppe->pkt_num_offset = 0; + ppe->pkt_numlen = 0; + ppe->pkt_num = 0; + ppe->sample_offset = 0; + ppe->cc = cc; +} + +int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd) { + ngtcp2_ssize rv; + ngtcp2_buf *buf = &ppe->buf; + ngtcp2_crypto_cc *cc = ppe->cc; + + if (ngtcp2_buf_left(buf) < cc->aead.max_overhead) { + return NGTCP2_ERR_NOBUF; + } + + if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { + ppe->len_offset = 1 + 4 + 1 + hd->dcid.datalen + 1 + hd->scid.datalen; + if (hd->type == NGTCP2_PKT_INITIAL) { + ppe->len_offset += ngtcp2_put_uvarintlen(hd->token.len) + hd->token.len; + } + ppe->pkt_num_offset = ppe->len_offset + NGTCP2_PKT_LENGTHLEN; + rv = ngtcp2_pkt_encode_hd_long( + buf->last, ngtcp2_buf_left(buf) - cc->aead.max_overhead, hd); + } else { + ppe->pkt_num_offset = 1 + hd->dcid.datalen; + rv = ngtcp2_pkt_encode_hd_short( + buf->last, ngtcp2_buf_left(buf) - cc->aead.max_overhead, hd); + } + if (rv < 0) { + return (int)rv; + } + + ppe->sample_offset = ppe->pkt_num_offset + 4; + + buf->last += rv; + + ppe->pkt_numlen = hd->pkt_numlen; + ppe->hdlen = (size_t)rv; + + ppe->pkt_num = hd->pkt_num; + + return 0; +} + +int ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe, ngtcp2_frame *fr) { + ngtcp2_ssize rv; + ngtcp2_buf *buf = &ppe->buf; + ngtcp2_crypto_cc *cc = ppe->cc; + + if (ngtcp2_buf_left(buf) < cc->aead.max_overhead) { + return NGTCP2_ERR_NOBUF; + } + + rv = ngtcp2_pkt_encode_frame( + buf->last, ngtcp2_buf_left(buf) - cc->aead.max_overhead, fr); + if (rv < 0) { + return (int)rv; + } + + buf->last += rv; + + return 0; +} + +ngtcp2_ssize ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt) { + ngtcp2_buf *buf = &ppe->buf; + ngtcp2_crypto_cc *cc = ppe->cc; + uint8_t *payload = buf->begin + ppe->hdlen; + size_t payloadlen = ngtcp2_buf_len(buf) - ppe->hdlen; + uint8_t mask[NGTCP2_HP_SAMPLELEN]; + uint8_t *p; + size_t i; + int rv; + + assert(cc->encrypt); + assert(cc->hp_mask); + + if (ppe->len_offset) { + ngtcp2_put_uvarint30( + buf->begin + ppe->len_offset, + (uint16_t)(payloadlen + ppe->pkt_numlen + cc->aead.max_overhead)); + } + + ngtcp2_crypto_create_nonce(ppe->nonce, cc->ckm->iv.base, cc->ckm->iv.len, + ppe->pkt_num); + + rv = cc->encrypt(payload, &cc->aead, &cc->ckm->aead_ctx, payload, payloadlen, + ppe->nonce, cc->ckm->iv.len, buf->begin, ppe->hdlen); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + buf->last = payload + payloadlen + cc->aead.max_overhead; + + /* TODO Check that we have enough space to get sample */ + assert(ppe->sample_offset + NGTCP2_HP_SAMPLELEN <= ngtcp2_buf_len(buf)); + + rv = cc->hp_mask(mask, &cc->hp, &cc->hp_ctx, buf->begin + ppe->sample_offset); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + p = buf->begin; + if (*p & NGTCP2_HEADER_FORM_BIT) { + *p = (uint8_t)(*p ^ (mask[0] & 0x0f)); + } else { + *p = (uint8_t)(*p ^ (mask[0] & 0x1f)); + } + + p = buf->begin + ppe->pkt_num_offset; + for (i = 0; i < ppe->pkt_numlen; ++i) { + *(p + i) ^= mask[i + 1]; + } + + if (ppkt != NULL) { + *ppkt = buf->begin; + } + + return (ngtcp2_ssize)ngtcp2_buf_len(buf); +} + +size_t ngtcp2_ppe_left(ngtcp2_ppe *ppe) { + ngtcp2_crypto_cc *cc = ppe->cc; + + if (ngtcp2_buf_left(&ppe->buf) < cc->aead.max_overhead) { + return 0; + } + + return ngtcp2_buf_left(&ppe->buf) - cc->aead.max_overhead; +} + +size_t ngtcp2_ppe_pktlen(ngtcp2_ppe *ppe) { + ngtcp2_crypto_cc *cc = ppe->cc; + + return ngtcp2_buf_len(&ppe->buf) + cc->aead.max_overhead; +} + +size_t ngtcp2_ppe_padding(ngtcp2_ppe *ppe) { + ngtcp2_crypto_cc *cc = ppe->cc; + ngtcp2_buf *buf = &ppe->buf; + size_t len; + + assert(ngtcp2_buf_left(buf) >= cc->aead.max_overhead); + + len = ngtcp2_buf_left(buf) - cc->aead.max_overhead; + memset(buf->last, 0, len); + buf->last += len; + + return len; +} + +size_t ngtcp2_ppe_padding_hp_sample(ngtcp2_ppe *ppe) { + ngtcp2_crypto_cc *cc = ppe->cc; + ngtcp2_buf *buf = &ppe->buf; + size_t max_samplelen; + size_t len = 0; + + assert(cc->aead.max_overhead); + + max_samplelen = + ngtcp2_buf_len(buf) + cc->aead.max_overhead - ppe->sample_offset; + if (max_samplelen < NGTCP2_HP_SAMPLELEN) { + len = NGTCP2_HP_SAMPLELEN - max_samplelen; + assert(ngtcp2_ppe_left(ppe) >= len); + memset(buf->last, 0, len); + buf->last += len; + } + + return len; +} + +size_t ngtcp2_ppe_padding_size(ngtcp2_ppe *ppe, size_t n) { + ngtcp2_crypto_cc *cc = ppe->cc; + ngtcp2_buf *buf = &ppe->buf; + size_t pktlen = ngtcp2_buf_len(buf) + cc->aead.max_overhead; + size_t len; + + if (pktlen >= n) { + return 0; + } + + len = n - pktlen; + buf->last = ngtcp2_setmem(buf->last, 0, len); + + return len; +} + +int ngtcp2_ppe_ensure_hp_sample(ngtcp2_ppe *ppe) { + ngtcp2_buf *buf = &ppe->buf; + return ngtcp2_buf_left(buf) >= (4 - ppe->pkt_numlen) + NGTCP2_HP_SAMPLELEN; +} diff --git a/lib/ngtcp2_ppe.h b/lib/ngtcp2_ppe.h new file mode 100644 index 0000000..bf220df --- /dev/null +++ b/lib/ngtcp2_ppe.h @@ -0,0 +1,153 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_PPE_H +#define NGTCP2_PPE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pkt.h" +#include "ngtcp2_buf.h" +#include "ngtcp2_crypto.h" + +/* + * ngtcp2_ppe is the Protected Packet Encoder. + */ +typedef struct ngtcp2_ppe { + ngtcp2_buf buf; + ngtcp2_crypto_cc *cc; + /* hdlen is the number of bytes for packet header written in buf. */ + size_t hdlen; + /* len_offset is the offset to Length field. */ + size_t len_offset; + /* pkt_num_offset is the offset to packet number field. */ + size_t pkt_num_offset; + /* pkt_numlen is the number of bytes used to encode a packet + number */ + size_t pkt_numlen; + /* sample_offset is the offset to sample for packet number + encryption. */ + size_t sample_offset; + /* pkt_num is the packet number written in buf. */ + int64_t pkt_num; + /* nonce is the buffer to store nonce. It should be equal or longer + than then length of IV. */ + uint8_t nonce[32]; +} ngtcp2_ppe; + +/* + * ngtcp2_ppe_init initializes |ppe| with the given buffer. + */ +void ngtcp2_ppe_init(ngtcp2_ppe *ppe, uint8_t *out, size_t outlen, + ngtcp2_crypto_cc *cc); + +/* + * ngtcp2_ppe_encode_hd encodes |hd|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * The buffer is too small. + */ +int ngtcp2_ppe_encode_hd(ngtcp2_ppe *ppe, const ngtcp2_pkt_hd *hd); + +/* + * ngtcp2_ppe_encode_frame encodes |fr|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOBUF + * The buffer is too small. + */ +int ngtcp2_ppe_encode_frame(ngtcp2_ppe *ppe, ngtcp2_frame *fr); + +/* + * ngtcp2_ppe_final encrypts QUIC packet payload. If |**ppkt| is not + * NULL, the pointer to the packet is assigned to it. + * + * This function returns the length of QUIC packet, including header, + * and payload if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User-defined callback function failed. + */ +ngtcp2_ssize ngtcp2_ppe_final(ngtcp2_ppe *ppe, const uint8_t **ppkt); + +/* + * ngtcp2_ppe_left returns the number of bytes left to write + * additional frames. This does not count AEAD overhead. + */ +size_t ngtcp2_ppe_left(ngtcp2_ppe *ppe); + +/* + * ngtcp2_ppe_pktlen returns the provisional packet length. It + * includes AEAD overhead. + */ +size_t ngtcp2_ppe_pktlen(ngtcp2_ppe *ppe); + +/** + * @function + * + * `ngtcp2_ppe_padding` encodes PADDING frames to the end of the + * buffer. This function returns the number of bytes padded. + */ +size_t ngtcp2_ppe_padding(ngtcp2_ppe *ppe); + +/* + * ngtcp2_ppe_padding_hp_sample adds PADDING frame if the current + * payload does not have enough space for header protection sample. + * This function should be called just before calling + * ngtcp2_ppe_final(). + * + * This function returns the number of bytes added as padding. + */ +size_t ngtcp2_ppe_padding_hp_sample(ngtcp2_ppe *ppe); + +/* + * ngtcp2_ppe_padding_size adds PADDING frame so that the size of QUIC + * packet is at least |n| bytes long. If it is unable to add PADDING + * in that way, this function still adds PADDING frame as much as + * possible. This function should be called just before calling + * ngtcp2_ppe_final(). For Short packet, this function should be + * called instead of ngtcp2_ppe_padding_hp_sample. + * + * This function returns the number of bytes added as padding. + */ +size_t ngtcp2_ppe_padding_size(ngtcp2_ppe *ppe, size_t n); + +/* + * ngtcp2_ppe_ensure_hp_sample returns nonzero if the buffer has + * enough space for header protection sample. This should be called + * right after packet header is written. + */ +int ngtcp2_ppe_ensure_hp_sample(ngtcp2_ppe *ppe); + +#endif /* NGTCP2_PPE_H */ diff --git a/lib/ngtcp2_pq.c b/lib/ngtcp2_pq.c new file mode 100644 index 0000000..5e1003d --- /dev/null +++ b/lib/ngtcp2_pq.c @@ -0,0 +1,164 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_pq.h" + +#include <assert.h> + +#include "ngtcp2_macro.h" + +void ngtcp2_pq_init(ngtcp2_pq *pq, ngtcp2_less less, const ngtcp2_mem *mem) { + pq->mem = mem; + pq->capacity = 0; + pq->q = NULL; + pq->length = 0; + pq->less = less; +} + +void ngtcp2_pq_free(ngtcp2_pq *pq) { + ngtcp2_mem_free(pq->mem, pq->q); + pq->q = NULL; +} + +static void swap(ngtcp2_pq *pq, size_t i, size_t j) { + ngtcp2_pq_entry *a = pq->q[i]; + ngtcp2_pq_entry *b = pq->q[j]; + + pq->q[i] = b; + b->index = i; + pq->q[j] = a; + a->index = j; +} + +static void bubble_up(ngtcp2_pq *pq, size_t index) { + size_t parent; + while (index != 0) { + parent = (index - 1) / 2; + if (!pq->less(pq->q[index], pq->q[parent])) { + return; + } + swap(pq, parent, index); + index = parent; + } +} + +int ngtcp2_pq_push(ngtcp2_pq *pq, ngtcp2_pq_entry *item) { + if (pq->capacity <= pq->length) { + void *nq; + size_t ncapacity; + + ncapacity = ngtcp2_max(4, (pq->capacity * 2)); + + nq = ngtcp2_mem_realloc(pq->mem, pq->q, + ncapacity * sizeof(ngtcp2_pq_entry *)); + if (nq == NULL) { + return NGTCP2_ERR_NOMEM; + } + pq->capacity = ncapacity; + pq->q = nq; + } + pq->q[pq->length] = item; + item->index = pq->length; + ++pq->length; + bubble_up(pq, pq->length - 1); + return 0; +} + +ngtcp2_pq_entry *ngtcp2_pq_top(ngtcp2_pq *pq) { + assert(pq->length); + return pq->q[0]; +} + +static void bubble_down(ngtcp2_pq *pq, size_t index) { + size_t i, j, minindex; + for (;;) { + j = index * 2 + 1; + minindex = index; + for (i = 0; i < 2; ++i, ++j) { + if (j >= pq->length) { + break; + } + if (pq->less(pq->q[j], pq->q[minindex])) { + minindex = j; + } + } + if (minindex == index) { + return; + } + swap(pq, index, minindex); + index = minindex; + } +} + +void ngtcp2_pq_pop(ngtcp2_pq *pq) { + if (pq->length > 0) { + pq->q[0] = pq->q[pq->length - 1]; + pq->q[0]->index = 0; + --pq->length; + bubble_down(pq, 0); + } +} + +void ngtcp2_pq_remove(ngtcp2_pq *pq, ngtcp2_pq_entry *item) { + assert(pq->q[item->index] == item); + + if (item->index == 0) { + ngtcp2_pq_pop(pq); + return; + } + + if (item->index == pq->length - 1) { + --pq->length; + return; + } + + pq->q[item->index] = pq->q[pq->length - 1]; + pq->q[item->index]->index = item->index; + --pq->length; + + if (pq->less(item, pq->q[item->index])) { + bubble_down(pq, item->index); + } else { + bubble_up(pq, item->index); + } +} + +int ngtcp2_pq_empty(ngtcp2_pq *pq) { return pq->length == 0; } + +size_t ngtcp2_pq_size(ngtcp2_pq *pq) { return pq->length; } + +int ngtcp2_pq_each(ngtcp2_pq *pq, ngtcp2_pq_item_cb fun, void *arg) { + size_t i; + + if (pq->length == 0) { + return 0; + } + for (i = 0; i < pq->length; ++i) { + if ((*fun)(pq->q[i], arg)) { + return 1; + } + } + return 0; +} diff --git a/lib/ngtcp2_pq.h b/lib/ngtcp2_pq.h new file mode 100644 index 0000000..720c309 --- /dev/null +++ b/lib/ngtcp2_pq.h @@ -0,0 +1,126 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_PQ_H +#define NGTCP2_PQ_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" + +/* Implementation of priority queue */ + +/* NGTCP2_PQ_BAD_INDEX is the priority queue index which indicates + that an entry is not queued. Assigning this value to + ngtcp2_pq_entry.index can check that the entry is queued or not. */ +#define NGTCP2_PQ_BAD_INDEX SIZE_MAX + +typedef struct ngtcp2_pq_entry { + size_t index; +} ngtcp2_pq_entry; + +/* "less" function, return nonzero if |lhs| is less than |rhs|. */ +typedef int (*ngtcp2_less)(const ngtcp2_pq_entry *lhs, + const ngtcp2_pq_entry *rhs); + +typedef struct ngtcp2_pq { + /* The pointer to the pointer to the item stored */ + ngtcp2_pq_entry **q; + /* Memory allocator */ + const ngtcp2_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 */ + ngtcp2_less less; +} ngtcp2_pq; + +/* + * Initializes priority queue |pq| with compare function |cmp|. + */ +void ngtcp2_pq_init(ngtcp2_pq *pq, ngtcp2_less less, const ngtcp2_mem *mem); + +/* + * Deallocates any resources allocated for |pq|. The stored items are + * not freed by this function. + */ +void ngtcp2_pq_free(ngtcp2_pq *pq); + +/* + * Adds |item| to the priority queue |pq|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_pq_push(ngtcp2_pq *pq, ngtcp2_pq_entry *item); + +/* + * Returns item at the top of the queue |pq|. It is undefined if the + * queue is empty. + */ +ngtcp2_pq_entry *ngtcp2_pq_top(ngtcp2_pq *pq); + +/* + * Pops item at the top of the queue |pq|. The popped item is not + * freed by this function. + */ +void ngtcp2_pq_pop(ngtcp2_pq *pq); + +/* + * Returns nonzero if the queue |pq| is empty. + */ +int ngtcp2_pq_empty(ngtcp2_pq *pq); + +/* + * Returns the number of items in the queue |pq|. + */ +size_t ngtcp2_pq_size(ngtcp2_pq *pq); + +typedef int (*ngtcp2_pq_item_cb)(ngtcp2_pq_entry *item, void *arg); + +/* + * Applys |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 ngtcp2_pq_each(ngtcp2_pq *pq, ngtcp2_pq_item_cb fun, void *arg); + +/* + * Removes |item| from priority queue. + */ +void ngtcp2_pq_remove(ngtcp2_pq *pq, ngtcp2_pq_entry *item); + +#endif /* NGTCP2_PQ_H */ diff --git a/lib/ngtcp2_pv.c b/lib/ngtcp2_pv.c new file mode 100644 index 0000000..314e005 --- /dev/null +++ b/lib/ngtcp2_pv.c @@ -0,0 +1,172 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_pv.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_log.h" +#include "ngtcp2_macro.h" +#include "ngtcp2_addr.h" + +void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, const uint8_t *data, + ngtcp2_tstamp expiry, uint8_t flags) { + memcpy(pvent->data, data, sizeof(pvent->data)); + pvent->expiry = expiry; + pvent->flags = flags; +} + +int ngtcp2_pv_new(ngtcp2_pv **ppv, const ngtcp2_dcid *dcid, + ngtcp2_duration timeout, uint8_t flags, ngtcp2_log *log, + const ngtcp2_mem *mem) { + (*ppv) = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_pv)); + if (*ppv == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_static_ringbuf_pv_ents_init(&(*ppv)->ents); + + ngtcp2_dcid_copy(&(*ppv)->dcid, dcid); + + (*ppv)->mem = mem; + (*ppv)->log = log; + (*ppv)->timeout = timeout; + (*ppv)->fallback_pto = 0; + (*ppv)->started_ts = UINT64_MAX; + (*ppv)->probe_pkt_left = NGTCP2_PV_NUM_PROBE_PKT; + (*ppv)->round = 0; + (*ppv)->flags = flags; + + return 0; +} + +void ngtcp2_pv_del(ngtcp2_pv *pv) { + if (pv == NULL) { + return; + } + ngtcp2_mem_free(pv->mem, pv); +} + +void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const uint8_t *data, + ngtcp2_tstamp expiry, uint8_t flags, + ngtcp2_tstamp ts) { + ngtcp2_pv_entry *ent; + + assert(pv->probe_pkt_left); + + if (ngtcp2_ringbuf_len(&pv->ents.rb) == 0) { + pv->started_ts = ts; + } + + ent = ngtcp2_ringbuf_push_back(&pv->ents.rb); + ngtcp2_pv_entry_init(ent, data, expiry, flags); + + pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_CANCEL_TIMER; + --pv->probe_pkt_left; +} + +int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, const uint8_t *data) { + size_t len = ngtcp2_ringbuf_len(&pv->ents.rb); + size_t i; + ngtcp2_pv_entry *ent; + + if (len == 0) { + return NGTCP2_ERR_INVALID_STATE; + } + + for (i = 0; i < len; ++i) { + ent = ngtcp2_ringbuf_get(&pv->ents.rb, i); + if (memcmp(ent->data, data, sizeof(ent->data)) == 0) { + *pflags = ent->flags; + ngtcp2_log_info(pv->log, NGTCP2_LOG_EVENT_PTV, "path has been validated"); + return 0; + } + } + + return NGTCP2_ERR_INVALID_ARGUMENT; +} + +void ngtcp2_pv_handle_entry_expiry(ngtcp2_pv *pv, ngtcp2_tstamp ts) { + ngtcp2_pv_entry *ent; + + if (ngtcp2_ringbuf_len(&pv->ents.rb) == 0) { + return; + } + + ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1); + + if (ent->expiry > ts) { + return; + } + + ++pv->round; + pv->probe_pkt_left = NGTCP2_PV_NUM_PROBE_PKT; +} + +int ngtcp2_pv_should_send_probe(ngtcp2_pv *pv) { + return pv->probe_pkt_left > 0; +} + +int ngtcp2_pv_validation_timed_out(ngtcp2_pv *pv, ngtcp2_tstamp ts) { + ngtcp2_tstamp t; + ngtcp2_pv_entry *ent; + + if (pv->started_ts == UINT64_MAX) { + return 0; + } + + assert(ngtcp2_ringbuf_len(&pv->ents.rb)); + + ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1); + + t = pv->started_ts + pv->timeout; + t = ngtcp2_max(t, ent->expiry); + + return t <= ts; +} + +ngtcp2_tstamp ngtcp2_pv_next_expiry(ngtcp2_pv *pv) { + ngtcp2_pv_entry *ent; + + if ((pv->flags & NGTCP2_PV_FLAG_CANCEL_TIMER) || + ngtcp2_ringbuf_len(&pv->ents.rb) == 0) { + return UINT64_MAX; + } + + ent = ngtcp2_ringbuf_get(&pv->ents.rb, ngtcp2_ringbuf_len(&pv->ents.rb) - 1); + + return ent->expiry; +} + +void ngtcp2_pv_cancel_expired_timer(ngtcp2_pv *pv, ngtcp2_tstamp ts) { + ngtcp2_tstamp expiry = ngtcp2_pv_next_expiry(pv); + + if (expiry > ts) { + return; + } + + pv->flags |= NGTCP2_PV_FLAG_CANCEL_TIMER; +} diff --git a/lib/ngtcp2_pv.h b/lib/ngtcp2_pv.h new file mode 100644 index 0000000..293cbca --- /dev/null +++ b/lib/ngtcp2_pv.h @@ -0,0 +1,198 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_PV_H +#define NGTCP2_PV_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_cid.h" +#include "ngtcp2_ringbuf.h" + +/* NGTCP2_PV_MAX_ENTRIES is the maximum number of entries that + ngtcp2_pv can contain. It must be power of 2. */ +#define NGTCP2_PV_MAX_ENTRIES 8 +/* NGTCP2_PV_NUM_PROBE_PKT is the number of probe packets containing + PATH_CHALLENGE sent at a time. */ +#define NGTCP2_PV_NUM_PROBE_PKT 2 + +typedef struct ngtcp2_log ngtcp2_log; + +typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; + +/* NGTCP2_PV_ENTRY_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_PV_ENTRY_FLAG_NONE 0x00u +/* NGTCP2_PV_ENTRY_FLAG_UNDERSIZED indicates that UDP datagram which + contains PATH_CHALLENGE is undersized (< 1200 bytes) */ +#define NGTCP2_PV_ENTRY_FLAG_UNDERSIZED 0x01u + +typedef struct ngtcp2_pv_entry { + /* expiry is the timestamp when this PATH_CHALLENGE expires. */ + ngtcp2_tstamp expiry; + /* flags is zero or more of NGTCP2_PV_ENTRY_FLAG_*. */ + uint8_t flags; + /* data is a byte string included in PATH_CHALLENGE. */ + uint8_t data[8]; +} ngtcp2_pv_entry; + +void ngtcp2_pv_entry_init(ngtcp2_pv_entry *pvent, const uint8_t *data, + ngtcp2_tstamp expiry, uint8_t flags); + +/* NGTCP2_PV_FLAG_NONE indicates no flag is set. */ +#define NGTCP2_PV_FLAG_NONE 0x00u +/* NGTCP2_PV_FLAG_DONT_CARE indicates that the outcome of path + validation should be ignored entirely. */ +#define NGTCP2_PV_FLAG_DONT_CARE 0x01u +/* NGTCP2_PV_FLAG_CANCEL_TIMER indicates that the expiry timer is + cancelled. */ +#define NGTCP2_PV_FLAG_CANCEL_TIMER 0x02u +/* NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE indicates that fallback DCID is + available in ngtcp2_pv. If path validation fails, fallback to the + fallback DCID. If path validation succeeds, fallback DCID is + retired if it does not equal to the current DCID. */ +#define NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE 0x04u +/* NGTCP2_PV_FLAG_MTU_PROBE indicates that a validation must probe + least MTU that QUIC requires, which is 1200 bytes. If it fails, a + path is not viable. */ +#define NGTCP2_PV_FLAG_MTU_PROBE 0x08u +/* NGTCP2_PV_FLAG_PREFERRED_ADDR indicates that client is migrating to + server's preferred address. This flag is only used by client. */ +#define NGTCP2_PV_FLAG_PREFERRED_ADDR 0x10u + +typedef struct ngtcp2_pv ngtcp2_pv; + +ngtcp2_static_ringbuf_def(pv_ents, NGTCP2_PV_MAX_ENTRIES, + sizeof(ngtcp2_pv_entry)); +/* + * ngtcp2_pv is the context of a single path validation. + */ +struct ngtcp2_pv { + const ngtcp2_mem *mem; + ngtcp2_log *log; + /* dcid is DCID and path this path validation uses. */ + ngtcp2_dcid dcid; + /* fallback_dcid is the usually validated DCID and used as a + fallback if this path validation fails. */ + ngtcp2_dcid fallback_dcid; + /* ents is the ring buffer of ngtcp2_pv_entry */ + ngtcp2_static_ringbuf_pv_ents ents; + /* timeout is the duration within which this path validation should + succeed. */ + ngtcp2_duration timeout; + /* fallback_pto is PTO of fallback connection. */ + ngtcp2_duration fallback_pto; + /* started_ts is the timestamp this path validation starts. */ + ngtcp2_tstamp started_ts; + /* round is the number of times that probe_pkt_left is reset. */ + size_t round; + /* probe_pkt_left is the number of probe packets containing + PATH_CHALLENGE which can be send without waiting for an + expiration of a previous flight. */ + size_t probe_pkt_left; + /* flags is bitwise-OR of zero or more of NGTCP2_PV_FLAG_*. */ + uint8_t flags; +}; + +/* + * ngtcp2_pv_new creates new ngtcp2_pv object and assigns its pointer + * to |*ppv|. This function makes a copy of |dcid|. |timeout| is a + * duration within which this path validation must succeed. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_pv_new(ngtcp2_pv **ppv, const ngtcp2_dcid *dcid, + ngtcp2_duration timeout, uint8_t flags, ngtcp2_log *log, + const ngtcp2_mem *mem); + +/* + * ngtcp2_pv_del deallocates |pv|. This function frees memory |pv| + * points too. + */ +void ngtcp2_pv_del(ngtcp2_pv *pv); + +/* + * ngtcp2_pv_add_entry adds new entry with |data|. |expiry| is the + * expiry time of the entry. + */ +void ngtcp2_pv_add_entry(ngtcp2_pv *pv, const uint8_t *data, + ngtcp2_tstamp expiry, uint8_t flags, ngtcp2_tstamp ts); + +/* + * ngtcp2_pv_full returns nonzero if |pv| is full of ngtcp2_pv_entry. + */ +int ngtcp2_pv_full(ngtcp2_pv *pv); + +/* + * ngtcp2_pv_validate validates that the received |data| matches the + * one of the existing entry. The flag of ngtcp2_pv_entry that + * matches |data| is assigned to |*pflags| if this function succeeds. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_PATH_VALIDATION_FAILED + * path validation has failed and must be abandoned + * NGTCP2_ERR_INVALID_STATE + * |pv| includes no entry + * NGTCP2_ERR_INVALID_ARGUMENT + * |pv| does not have an entry which has |data| and |path| + */ +int ngtcp2_pv_validate(ngtcp2_pv *pv, uint8_t *pflags, const uint8_t *data); + +/* + * ngtcp2_pv_handle_entry_expiry checks expiry of existing entries. + */ +void ngtcp2_pv_handle_entry_expiry(ngtcp2_pv *pv, ngtcp2_tstamp ts); + +/* + * ngtcp2_pv_should_send_probe returns nonzero if new entry can be + * added by ngtcp2_pv_add_entry. + */ +int ngtcp2_pv_should_send_probe(ngtcp2_pv *pv); + +/* + * ngtcp2_pv_validation_timed_out returns nonzero if the path + * validation fails because of timeout. + */ +int ngtcp2_pv_validation_timed_out(ngtcp2_pv *pv, ngtcp2_tstamp ts); + +/* + * ngtcp2_pv_next_expiry returns the earliest expiry. + */ +ngtcp2_tstamp ngtcp2_pv_next_expiry(ngtcp2_pv *pv); + +/* + * ngtcp2_pv_cancel_expired_timer cancels the expired timer. + */ +void ngtcp2_pv_cancel_expired_timer(ngtcp2_pv *pv, ngtcp2_tstamp ts); + +#endif /* NGTCP2_PV_H */ diff --git a/lib/ngtcp2_qlog.c b/lib/ngtcp2_qlog.c new file mode 100644 index 0000000..c83f885 --- /dev/null +++ b/lib/ngtcp2_qlog.c @@ -0,0 +1,1219 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_qlog.h" + +#include <assert.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_net.h" +#include "ngtcp2_unreachable.h" + +void ngtcp2_qlog_init(ngtcp2_qlog *qlog, ngtcp2_qlog_write write, + ngtcp2_tstamp ts, void *user_data) { + qlog->write = write; + qlog->ts = qlog->last_ts = ts; + qlog->user_data = user_data; +} + +#define write_verbatim(DEST, S) ngtcp2_cpymem((DEST), (S), sizeof(S) - 1) + +static uint8_t *write_string_impl(uint8_t *p, const uint8_t *data, + size_t datalen) { + *p++ = '"'; + if (datalen) { + p = ngtcp2_cpymem(p, data, datalen); + } + *p++ = '"'; + return p; +} + +#define write_string(DEST, S) \ + write_string_impl((DEST), (const uint8_t *)(S), sizeof(S) - 1) + +#define NGTCP2_LOWER_XDIGITS "0123456789abcdef" + +static uint8_t *write_hex(uint8_t *p, const uint8_t *data, size_t datalen) { + const uint8_t *b = data, *end = data + datalen; + *p++ = '"'; + for (; b != end; ++b) { + *p++ = (uint8_t)NGTCP2_LOWER_XDIGITS[*b >> 4]; + *p++ = (uint8_t)NGTCP2_LOWER_XDIGITS[*b & 0xf]; + } + *p++ = '"'; + return p; +} + +static uint8_t *write_cid(uint8_t *p, const ngtcp2_cid *cid) { + return write_hex(p, cid->data, cid->datalen); +} + +static uint8_t *write_number(uint8_t *p, uint64_t n) { + size_t nlen = 0; + uint64_t t; + uint8_t *res; + + if (n == 0) { + *p++ = '0'; + return p; + } + for (t = n; t; t /= 10, ++nlen) + ; + p += nlen; + res = p; + for (; n; n /= 10) { + *--p = (uint8_t)((n % 10) + '0'); + } + return res; +} + +static uint8_t *write_tstamp(uint8_t *p, ngtcp2_tstamp ts) { + return write_number(p, ts / NGTCP2_MILLISECONDS); +} + +static uint8_t *write_duration(uint8_t *p, ngtcp2_duration duration) { + return write_number(p, duration / NGTCP2_MILLISECONDS); +} + +static uint8_t *write_bool(uint8_t *p, int b) { + if (b) { + return ngtcp2_cpymem(p, "true", sizeof("true") - 1); + } + return ngtcp2_cpymem(p, "false", sizeof("false") - 1); +} + +static uint8_t *write_pair_impl(uint8_t *p, const uint8_t *name, size_t namelen, + const ngtcp2_vec *value) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_string_impl(p, value->base, value->len); +} + +#define write_pair(DEST, NAME, VALUE) \ + write_pair_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, (VALUE)) + +static uint8_t *write_pair_hex_impl(uint8_t *p, const uint8_t *name, + size_t namelen, const uint8_t *value, + size_t valuelen) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_hex(p, value, valuelen); +} + +#define write_pair_hex(DEST, NAME, VALUE, VALUELEN) \ + write_pair_hex_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE), (VALUELEN)) + +static uint8_t *write_pair_number_impl(uint8_t *p, const uint8_t *name, + size_t namelen, uint64_t value) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_number(p, value); +} + +#define write_pair_number(DEST, NAME, VALUE) \ + write_pair_number_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +static uint8_t *write_pair_duration_impl(uint8_t *p, const uint8_t *name, + size_t namelen, + ngtcp2_duration duration) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_duration(p, duration); +} + +#define write_pair_duration(DEST, NAME, VALUE) \ + write_pair_duration_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +static uint8_t *write_pair_tstamp_impl(uint8_t *p, const uint8_t *name, + size_t namelen, ngtcp2_tstamp ts) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_tstamp(p, ts); +} + +#define write_pair_tstamp(DEST, NAME, VALUE) \ + write_pair_tstamp_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +static uint8_t *write_pair_bool_impl(uint8_t *p, const uint8_t *name, + size_t namelen, int b) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_bool(p, b); +} + +#define write_pair_bool(DEST, NAME, VALUE) \ + write_pair_bool_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +static uint8_t *write_pair_cid_impl(uint8_t *p, const uint8_t *name, + size_t namelen, const ngtcp2_cid *cid) { + p = write_string_impl(p, name, namelen); + *p++ = ':'; + return write_cid(p, cid); +} + +#define write_pair_cid(DEST, NAME, VALUE) \ + write_pair_cid_impl((DEST), (const uint8_t *)(NAME), sizeof(NAME) - 1, \ + (VALUE)) + +#define ngtcp2_make_vec_lit(S) \ + { (uint8_t *)(S), sizeof((S)) - 1 } + +static uint8_t *write_common_fields(uint8_t *p, const ngtcp2_cid *odcid) { + p = write_verbatim( + p, "\"common_fields\":{\"protocol_type\":[\"QUIC\"],\"time_format\":" + "\"relative\",\"reference_time\":0,\"group_id\":"); + p = write_cid(p, odcid); + *p++ = '}'; + return p; +} + +static uint8_t *write_trace(uint8_t *p, int server, const ngtcp2_cid *odcid) { + p = write_verbatim( + p, "\"trace\":{\"vantage_point\":{\"name\":\"ngtcp2\",\"type\":"); + if (server) { + p = write_string(p, "server"); + } else { + p = write_string(p, "client"); + } + p = write_verbatim(p, "},"); + p = write_common_fields(p, odcid); + *p++ = '}'; + return p; +} + +void ngtcp2_qlog_start(ngtcp2_qlog *qlog, const ngtcp2_cid *odcid, int server) { + uint8_t buf[1024]; + uint8_t *p = buf; + + if (!qlog->write) { + return; + } + + p = write_verbatim( + p, "\x1e{\"qlog_format\":\"JSON-SEQ\",\"qlog_version\":\"0.3\","); + p = write_trace(p, server, odcid); + p = write_verbatim(p, "}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +void ngtcp2_qlog_end(ngtcp2_qlog *qlog) { + uint8_t buf[1] = {0}; + + if (!qlog->write) { + return; + } + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_FIN, &buf, 0); +} + +static ngtcp2_vec vec_pkt_type_initial = ngtcp2_make_vec_lit("initial"); +static ngtcp2_vec vec_pkt_type_handshake = ngtcp2_make_vec_lit("handshake"); +static ngtcp2_vec vec_pkt_type_0rtt = ngtcp2_make_vec_lit("0RTT"); +static ngtcp2_vec vec_pkt_type_1rtt = ngtcp2_make_vec_lit("1RTT"); +static ngtcp2_vec vec_pkt_type_retry = ngtcp2_make_vec_lit("retry"); +static ngtcp2_vec vec_pkt_type_version_negotiation = + ngtcp2_make_vec_lit("version_negotiation"); +static ngtcp2_vec vec_pkt_type_stateless_reset = + ngtcp2_make_vec_lit("stateless_reset"); +static ngtcp2_vec vec_pkt_type_unknown = ngtcp2_make_vec_lit("unknown"); + +static const ngtcp2_vec *qlog_pkt_type(const ngtcp2_pkt_hd *hd) { + if (hd->flags & NGTCP2_PKT_FLAG_LONG_FORM) { + switch (hd->type) { + case NGTCP2_PKT_INITIAL: + return &vec_pkt_type_initial; + case NGTCP2_PKT_HANDSHAKE: + return &vec_pkt_type_handshake; + case NGTCP2_PKT_0RTT: + return &vec_pkt_type_0rtt; + case NGTCP2_PKT_RETRY: + return &vec_pkt_type_retry; + default: + return &vec_pkt_type_unknown; + } + } + + switch (hd->type) { + case NGTCP2_PKT_VERSION_NEGOTIATION: + return &vec_pkt_type_version_negotiation; + case NGTCP2_PKT_STATELESS_RESET: + return &vec_pkt_type_stateless_reset; + case NGTCP2_PKT_1RTT: + return &vec_pkt_type_1rtt; + default: + return &vec_pkt_type_unknown; + } +} + +static uint8_t *write_pkt_hd(uint8_t *p, const ngtcp2_pkt_hd *hd) { + /* + * {"packet_type":"version_negotiation","packet_number":"0000000000000000000","token":{"data":""}} + */ +#define NGTCP2_QLOG_PKT_HD_OVERHEAD 95 + + *p++ = '{'; + p = write_pair(p, "packet_type", qlog_pkt_type(hd)); + *p++ = ','; + p = write_pair_number(p, "packet_number", (uint64_t)hd->pkt_num); + if (hd->type == NGTCP2_PKT_INITIAL && hd->token.len) { + p = write_verbatim(p, ",\"token\":{"); + p = write_pair_hex(p, "data", hd->token.base, hd->token.len); + *p++ = '}'; + } + /* TODO Write DCIL and DCID */ + /* TODO Write SCIL and SCID */ + *p++ = '}'; + return p; +} + +static uint8_t *write_padding_frame(uint8_t *p, const ngtcp2_padding *fr) { + (void)fr; + + /* {"frame_type":"padding"} */ +#define NGTCP2_QLOG_PADDING_FRAME_OVERHEAD 24 + + return write_verbatim(p, "{\"frame_type\":\"padding\"}"); +} + +static uint8_t *write_ping_frame(uint8_t *p, const ngtcp2_ping *fr) { + (void)fr; + + /* {"frame_type":"ping"} */ +#define NGTCP2_QLOG_PING_FRAME_OVERHEAD 21 + + return write_verbatim(p, "{\"frame_type\":\"ping\"}"); +} + +static uint8_t *write_ack_frame(uint8_t *p, const ngtcp2_ack *fr) { + int64_t largest_ack, min_ack; + size_t i; + const ngtcp2_ack_range *range; + + /* + * {"frame_type":"ack","ack_delay":0000000000000000000,"acked_ranges":[]} + * + * each range: + * [0000000000000000000,0000000000000000000], + * + * ecn: + * ,"ect1":0000000000000000000,"ect0":0000000000000000000,"ce":0000000000000000000 + */ +#define NGTCP2_QLOG_ACK_FRAME_BASE_OVERHEAD 70 +#define NGTCP2_QLOG_ACK_FRAME_RANGE_OVERHEAD 42 +#define NGTCP2_QLOG_ACK_FRAME_ECN_OVERHEAD 79 + + p = write_verbatim(p, "{\"frame_type\":\"ack\","); + p = write_pair_duration(p, "ack_delay", fr->ack_delay_unscaled); + p = write_verbatim(p, ",\"acked_ranges\":["); + + largest_ack = fr->largest_ack; + min_ack = fr->largest_ack - (int64_t)fr->first_ack_range; + + *p++ = '['; + p = write_number(p, (uint64_t)min_ack); + if (largest_ack != min_ack) { + *p++ = ','; + p = write_number(p, (uint64_t)largest_ack); + } + *p++ = ']'; + + for (i = 0; i < fr->rangecnt; ++i) { + range = &fr->ranges[i]; + largest_ack = min_ack - (int64_t)range->gap - 2; + min_ack = largest_ack - (int64_t)range->len; + *p++ = ','; + *p++ = '['; + p = write_number(p, (uint64_t)min_ack); + if (largest_ack != min_ack) { + *p++ = ','; + p = write_number(p, (uint64_t)largest_ack); + } + *p++ = ']'; + } + + *p++ = ']'; + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { + *p++ = ','; + p = write_pair_number(p, "ect1", fr->ecn.ect1); + *p++ = ','; + p = write_pair_number(p, "ect0", fr->ecn.ect0); + *p++ = ','; + p = write_pair_number(p, "ce", fr->ecn.ce); + } + + *p++ = '}'; + + return p; +} + +static uint8_t *write_reset_stream_frame(uint8_t *p, + const ngtcp2_reset_stream *fr) { + /* + * {"frame_type":"reset_stream","stream_id":0000000000000000000,"error_code":0000000000000000000,"final_size":0000000000000000000} + */ +#define NGTCP2_QLOG_RESET_STREAM_FRAME_OVERHEAD 127 + + p = write_verbatim(p, "{\"frame_type\":\"reset_stream\","); + p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id); + *p++ = ','; + p = write_pair_number(p, "error_code", fr->app_error_code); + *p++ = ','; + p = write_pair_number(p, "final_size", fr->final_size); + *p++ = '}'; + + return p; +} + +static uint8_t *write_stop_sending_frame(uint8_t *p, + const ngtcp2_stop_sending *fr) { + /* + * {"frame_type":"stop_sending","stream_id":0000000000000000000,"error_code":0000000000000000000} + */ +#define NGTCP2_QLOG_STOP_SENDING_FRAME_OVERHEAD 94 + + p = write_verbatim(p, "{\"frame_type\":\"stop_sending\","); + p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id); + *p++ = ','; + p = write_pair_number(p, "error_code", fr->app_error_code); + *p++ = '}'; + + return p; +} + +static uint8_t *write_crypto_frame(uint8_t *p, const ngtcp2_crypto *fr) { + /* + * {"frame_type":"crypto","offset":0000000000000000000,"length":0000000000000000000} + */ +#define NGTCP2_QLOG_CRYPTO_FRAME_OVERHEAD 81 + + p = write_verbatim(p, "{\"frame_type\":\"crypto\","); + p = write_pair_number(p, "offset", fr->offset); + *p++ = ','; + p = write_pair_number(p, "length", ngtcp2_vec_len(fr->data, fr->datacnt)); + *p++ = '}'; + + return p; +} + +static uint8_t *write_new_token_frame(uint8_t *p, const ngtcp2_new_token *fr) { + /* + * {"frame_type":"new_token","length":0000000000000000000,"token":{"data":""}} + */ +#define NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD 75 + + p = write_verbatim(p, "{\"frame_type\":\"new_token\","); + p = write_pair_number(p, "length", fr->token.len); + p = write_verbatim(p, ",\"token\":{"); + p = write_pair_hex(p, "data", fr->token.base, fr->token.len); + *p++ = '}'; + *p++ = '}'; + + return p; +} + +static uint8_t *write_stream_frame(uint8_t *p, const ngtcp2_stream *fr) { + /* + * {"frame_type":"stream","stream_id":0000000000000000000,"offset":0000000000000000000,"length":0000000000000000000,"fin":true} + */ +#define NGTCP2_QLOG_STREAM_FRAME_OVERHEAD 124 + + p = write_verbatim(p, "{\"frame_type\":\"stream\","); + p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id); + *p++ = ','; + p = write_pair_number(p, "offset", fr->offset); + *p++ = ','; + p = write_pair_number(p, "length", ngtcp2_vec_len(fr->data, fr->datacnt)); + if (fr->fin) { + *p++ = ','; + p = write_pair_bool(p, "fin", 1); + } + *p++ = '}'; + + return p; +} + +static uint8_t *write_max_data_frame(uint8_t *p, const ngtcp2_max_data *fr) { + /* + * {"frame_type":"max_data","maximum":0000000000000000000} + */ +#define NGTCP2_QLOG_MAX_DATA_FRAME_OVERHEAD 55 + + p = write_verbatim(p, "{\"frame_type\":\"max_data\","); + p = write_pair_number(p, "maximum", fr->max_data); + *p++ = '}'; + + return p; +} + +static uint8_t *write_max_stream_data_frame(uint8_t *p, + const ngtcp2_max_stream_data *fr) { + /* + * {"frame_type":"max_stream_data","stream_id":0000000000000000000,"maximum":0000000000000000000} + */ +#define NGTCP2_QLOG_MAX_STREAM_DATA_FRAME_OVERHEAD 94 + + p = write_verbatim(p, "{\"frame_type\":\"max_stream_data\","); + p = write_pair_number(p, "stream_id", (uint64_t)fr->stream_id); + *p++ = ','; + p = write_pair_number(p, "maximum", fr->max_stream_data); + *p++ = '}'; + + return p; +} + +static uint8_t *write_max_streams_frame(uint8_t *p, + const ngtcp2_max_streams *fr) { + /* + * {"frame_type":"max_streams","stream_type":"unidirectional","maximum":0000000000000000000} + */ +#define NGTCP2_QLOG_MAX_STREAMS_FRAME_OVERHEAD 89 + + p = write_verbatim(p, "{\"frame_type\":\"max_streams\",\"stream_type\":"); + if (fr->type == NGTCP2_FRAME_MAX_STREAMS_BIDI) { + p = write_string(p, "bidirectional"); + } else { + p = write_string(p, "unidirectional"); + } + *p++ = ','; + p = write_pair_number(p, "maximum", fr->max_streams); + *p++ = '}'; + + return p; +} + +static uint8_t *write_data_blocked_frame(uint8_t *p, + const ngtcp2_data_blocked *fr) { + (void)fr; + + /* + * {"frame_type":"data_blocked"} + */ +#define NGTCP2_QLOG_DATA_BLOCKED_FRAME_OVERHEAD 29 + + /* TODO log limit */ + + return write_verbatim(p, "{\"frame_type\":\"data_blocked\"}"); +} + +static uint8_t * +write_stream_data_blocked_frame(uint8_t *p, + const ngtcp2_stream_data_blocked *fr) { + (void)fr; + + /* + * {"frame_type":"stream_data_blocked"} + */ +#define NGTCP2_QLOG_STREAM_DATA_BLOCKED_FRAME_OVERHEAD 36 + + /* TODO log limit */ + + return write_verbatim(p, "{\"frame_type\":\"stream_data_blocked\"}"); +} + +static uint8_t *write_streams_blocked_frame(uint8_t *p, + const ngtcp2_streams_blocked *fr) { + (void)fr; + + /* + * {"frame_type":"streams_blocked"} + */ +#define NGTCP2_QLOG_STREAMS_BLOCKED_FRAME_OVERHEAD 32 + + /* TODO Log stream_type and limit */ + + return write_verbatim(p, "{\"frame_type\":\"streams_blocked\"}"); +} + +static uint8_t * +write_new_connection_id_frame(uint8_t *p, const ngtcp2_new_connection_id *fr) { + /* + * {"frame_type":"new_connection_id","sequence_number":0000000000000000000,"retire_prior_to":0000000000000000000,"connection_id_length":0000000000000000000,"connection_id":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","stateless_reset_token":{"data":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}} + */ +#define NGTCP2_QLOG_NEW_CONNECTION_ID_FRAME_OVERHEAD 280 + + p = write_verbatim(p, "{\"frame_type\":\"new_connection_id\","); + p = write_pair_number(p, "sequence_number", fr->seq); + *p++ = ','; + p = write_pair_number(p, "retire_prior_to", fr->retire_prior_to); + *p++ = ','; + p = write_pair_number(p, "connection_id_length", fr->cid.datalen); + *p++ = ','; + p = write_pair_cid(p, "connection_id", &fr->cid); + p = write_verbatim(p, ",\"stateless_reset_token\":{"); + p = write_pair_hex(p, "data", fr->stateless_reset_token, + sizeof(fr->stateless_reset_token)); + *p++ = '}'; + *p++ = '}'; + + return p; +} + +static uint8_t * +write_retire_connection_id_frame(uint8_t *p, + const ngtcp2_retire_connection_id *fr) { + /* + * {"frame_type":"retire_connection_id","sequence_number":0000000000000000000} + */ +#define NGTCP2_QLOG_RETIRE_CONNECTION_ID_FRAME_OVERHEAD 75 + + p = write_verbatim(p, "{\"frame_type\":\"retire_connection_id\","); + p = write_pair_number(p, "sequence_number", fr->seq); + *p++ = '}'; + + return p; +} + +static uint8_t *write_path_challenge_frame(uint8_t *p, + const ngtcp2_path_challenge *fr) { + /* + * {"frame_type":"path_challenge","data":"xxxxxxxxxxxxxxxx"} + */ +#define NGTCP2_QLOG_PATH_CHALLENGE_FRAME_OVERHEAD 57 + + p = write_verbatim(p, "{\"frame_type\":\"path_challenge\","); + p = write_pair_hex(p, "data", fr->data, sizeof(fr->data)); + *p++ = '}'; + + return p; +} + +static uint8_t *write_path_response_frame(uint8_t *p, + const ngtcp2_path_response *fr) { + /* + * {"frame_type":"path_response","data":"xxxxxxxxxxxxxxxx"} + */ +#define NGTCP2_QLOG_PATH_RESPONSE_FRAME_OVERHEAD 56 + + p = write_verbatim(p, "{\"frame_type\":\"path_response\","); + p = write_pair_hex(p, "data", fr->data, sizeof(fr->data)); + *p++ = '}'; + + return p; +} + +static uint8_t * +write_connection_close_frame(uint8_t *p, const ngtcp2_connection_close *fr) { + /* + * {"frame_type":"connection_close","error_space":"application","error_code":0000000000000000000,"raw_error_code":0000000000000000000} + */ +#define NGTCP2_QLOG_CONNECTION_CLOSE_FRAME_OVERHEAD 131 + + p = write_verbatim(p, + "{\"frame_type\":\"connection_close\",\"error_space\":"); + if (fr->type == NGTCP2_FRAME_CONNECTION_CLOSE) { + p = write_string(p, "transport"); + } else { + p = write_string(p, "application"); + } + *p++ = ','; + p = write_pair_number(p, "error_code", fr->error_code); + *p++ = ','; + p = write_pair_number(p, "raw_error_code", fr->error_code); + /* TODO Write reason by escaping non-printables */ + /* TODO Write trigger_frame_type */ + *p++ = '}'; + + return p; +} + +static uint8_t *write_handshake_done_frame(uint8_t *p, + const ngtcp2_handshake_done *fr) { + (void)fr; + + /* + * {"frame_type":"handshake_done"} + */ +#define NGTCP2_QLOG_HANDSHAKE_DONE_FRAME_OVERHEAD 31 + + return write_verbatim(p, "{\"frame_type\":\"handshake_done\"}"); +} + +static uint8_t *write_datagram_frame(uint8_t *p, const ngtcp2_datagram *fr) { + /* + * {"frame_type":"datagram","length":0000000000000000000} + */ +#define NGTCP2_QLOG_DATAGRAM_FRAME_OVERHEAD 54 + + p = write_verbatim(p, "{\"frame_type\":\"datagram\","); + p = write_pair_number(p, "length", ngtcp2_vec_len(fr->data, fr->datacnt)); + *p++ = '}'; + + return p; +} + +static uint8_t *qlog_write_time(ngtcp2_qlog *qlog, uint8_t *p) { + return write_pair_tstamp(p, "time", qlog->last_ts - qlog->ts); +} + +static void qlog_pkt_write_start(ngtcp2_qlog *qlog, int sent) { + uint8_t *p; + + if (!qlog->write) { + return; + } + + ngtcp2_buf_reset(&qlog->buf); + p = qlog->buf.last; + + *p++ = '\x1e'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim(p, ",\"name\":"); + if (sent) { + p = write_string(p, "transport:packet_sent"); + } else { + p = write_string(p, "transport:packet_received"); + } + p = write_verbatim(p, ",\"data\":{\"frames\":["); + qlog->buf.last = p; +} + +static void qlog_pkt_write_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen) { + uint8_t *p = qlog->buf.last; + + if (!qlog->write) { + return; + } + + /* + * ],"header":,"raw":{"length":0000000000000000000}}} + * + * plus, terminating LF + */ +#define NGTCP2_QLOG_PKT_WRITE_END_OVERHEAD \ + (1 + 50 + NGTCP2_QLOG_PKT_HD_OVERHEAD) + + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_PKT_WRITE_END_OVERHEAD + hd->token.len * 2) { + return; + } + + assert(ngtcp2_buf_len(&qlog->buf)); + + /* Eat last ',' */ + if (*(p - 1) == ',') { + --p; + } + + p = write_verbatim(p, "],\"header\":"); + p = write_pkt_hd(p, hd); + p = write_verbatim(p, ",\"raw\":{\"length\":"); + p = write_number(p, pktlen); + p = write_verbatim(p, "}}}\n"); + + qlog->buf.last = p; + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, qlog->buf.pos, + ngtcp2_buf_len(&qlog->buf)); +} + +void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr) { + uint8_t *p = qlog->buf.last; + + if (!qlog->write) { + return; + } + + switch (fr->type) { + case NGTCP2_FRAME_PADDING: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_PADDING_FRAME_OVERHEAD + 1) { + return; + } + p = write_padding_frame(p, &fr->padding); + break; + case NGTCP2_FRAME_PING: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_PING_FRAME_OVERHEAD + 1) { + return; + } + p = write_ping_frame(p, &fr->ping); + break; + case NGTCP2_FRAME_ACK: + case NGTCP2_FRAME_ACK_ECN: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_ACK_FRAME_BASE_OVERHEAD + + (size_t)(fr->type == NGTCP2_FRAME_ACK_ECN + ? NGTCP2_QLOG_ACK_FRAME_ECN_OVERHEAD + : 0) + + NGTCP2_QLOG_ACK_FRAME_RANGE_OVERHEAD * (1 + fr->ack.rangecnt) + 1) { + return; + } + p = write_ack_frame(p, &fr->ack); + break; + case NGTCP2_FRAME_RESET_STREAM: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_RESET_STREAM_FRAME_OVERHEAD + 1) { + return; + } + p = write_reset_stream_frame(p, &fr->reset_stream); + break; + case NGTCP2_FRAME_STOP_SENDING: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_STOP_SENDING_FRAME_OVERHEAD + 1) { + return; + } + p = write_stop_sending_frame(p, &fr->stop_sending); + break; + case NGTCP2_FRAME_CRYPTO: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_CRYPTO_FRAME_OVERHEAD + 1) { + return; + } + p = write_crypto_frame(p, &fr->crypto); + break; + case NGTCP2_FRAME_NEW_TOKEN: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD + + fr->new_token.token.len * 2 + 1) { + return; + } + p = write_new_token_frame(p, &fr->new_token); + break; + case NGTCP2_FRAME_STREAM: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_STREAM_FRAME_OVERHEAD + 1) { + return; + } + p = write_stream_frame(p, &fr->stream); + break; + case NGTCP2_FRAME_MAX_DATA: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_MAX_DATA_FRAME_OVERHEAD + 1) { + return; + } + p = write_max_data_frame(p, &fr->max_data); + break; + case NGTCP2_FRAME_MAX_STREAM_DATA: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_MAX_STREAM_DATA_FRAME_OVERHEAD + 1) { + return; + } + p = write_max_stream_data_frame(p, &fr->max_stream_data); + break; + case NGTCP2_FRAME_MAX_STREAMS_BIDI: + case NGTCP2_FRAME_MAX_STREAMS_UNI: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_MAX_STREAMS_FRAME_OVERHEAD + 1) { + return; + } + p = write_max_streams_frame(p, &fr->max_streams); + break; + case NGTCP2_FRAME_DATA_BLOCKED: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_DATA_BLOCKED_FRAME_OVERHEAD + 1) { + return; + } + p = write_data_blocked_frame(p, &fr->data_blocked); + break; + case NGTCP2_FRAME_STREAM_DATA_BLOCKED: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_STREAM_DATA_BLOCKED_FRAME_OVERHEAD + 1) { + return; + } + p = write_stream_data_blocked_frame(p, &fr->stream_data_blocked); + break; + case NGTCP2_FRAME_STREAMS_BLOCKED_BIDI: + case NGTCP2_FRAME_STREAMS_BLOCKED_UNI: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_STREAMS_BLOCKED_FRAME_OVERHEAD + 1) { + return; + } + p = write_streams_blocked_frame(p, &fr->streams_blocked); + break; + case NGTCP2_FRAME_NEW_CONNECTION_ID: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_NEW_CONNECTION_ID_FRAME_OVERHEAD + 1) { + return; + } + p = write_new_connection_id_frame(p, &fr->new_connection_id); + break; + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_RETIRE_CONNECTION_ID_FRAME_OVERHEAD + 1) { + return; + } + p = write_retire_connection_id_frame(p, &fr->retire_connection_id); + break; + case NGTCP2_FRAME_PATH_CHALLENGE: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_PATH_CHALLENGE_FRAME_OVERHEAD + 1) { + return; + } + p = write_path_challenge_frame(p, &fr->path_challenge); + break; + case NGTCP2_FRAME_PATH_RESPONSE: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_PATH_RESPONSE_FRAME_OVERHEAD + 1) { + return; + } + p = write_path_response_frame(p, &fr->path_response); + break; + case NGTCP2_FRAME_CONNECTION_CLOSE: + case NGTCP2_FRAME_CONNECTION_CLOSE_APP: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_CONNECTION_CLOSE_FRAME_OVERHEAD + 1) { + return; + } + p = write_connection_close_frame(p, &fr->connection_close); + break; + case NGTCP2_FRAME_HANDSHAKE_DONE: + if (ngtcp2_buf_left(&qlog->buf) < + NGTCP2_QLOG_HANDSHAKE_DONE_FRAME_OVERHEAD + 1) { + return; + } + p = write_handshake_done_frame(p, &fr->handshake_done); + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_DATAGRAM_FRAME_OVERHEAD + 1) { + return; + } + p = write_datagram_frame(p, &fr->datagram); + break; + default: + ngtcp2_unreachable(); + } + + *p++ = ','; + + qlog->buf.last = p; +} + +void ngtcp2_qlog_pkt_received_start(ngtcp2_qlog *qlog) { + qlog_pkt_write_start(qlog, /* sent = */ 0); +} + +void ngtcp2_qlog_pkt_received_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen) { + qlog_pkt_write_end(qlog, hd, pktlen); +} + +void ngtcp2_qlog_pkt_sent_start(ngtcp2_qlog *qlog) { + qlog_pkt_write_start(qlog, /* sent = */ 1); +} + +void ngtcp2_qlog_pkt_sent_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen) { + qlog_pkt_write_end(qlog, hd, pktlen); +} + +void ngtcp2_qlog_parameters_set_transport_params( + ngtcp2_qlog *qlog, const ngtcp2_transport_params *params, int server, + ngtcp2_qlog_side side) { + uint8_t buf[1024]; + uint8_t *p = buf; + const ngtcp2_preferred_addr *paddr; + const ngtcp2_sockaddr_in *sa_in; + const ngtcp2_sockaddr_in6 *sa_in6; + + if (!qlog->write) { + return; + } + + *p++ = '\x1e'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim( + p, ",\"name\":\"transport:parameters_set\",\"data\":{\"owner\":"); + + if (side == NGTCP2_QLOG_SIDE_LOCAL) { + p = write_string(p, "local"); + } else { + p = write_string(p, "remote"); + } + + *p++ = ','; + p = write_pair_cid(p, "initial_source_connection_id", ¶ms->initial_scid); + *p++ = ','; + if (side == (server ? NGTCP2_QLOG_SIDE_LOCAL : NGTCP2_QLOG_SIDE_REMOTE)) { + p = write_pair_cid(p, "original_destination_connection_id", + ¶ms->original_dcid); + *p++ = ','; + } + if (params->retry_scid_present) { + p = write_pair_cid(p, "retry_source_connection_id", ¶ms->retry_scid); + *p++ = ','; + } + if (params->stateless_reset_token_present) { + p = write_verbatim(p, "\"stateless_reset_token\":{"); + p = write_pair_hex(p, "data", params->stateless_reset_token, + sizeof(params->stateless_reset_token)); + *p++ = '}'; + *p++ = ','; + } + p = write_pair_bool(p, "disable_active_migration", + params->disable_active_migration); + *p++ = ','; + p = write_pair_duration(p, "max_idle_timeout", params->max_idle_timeout); + *p++ = ','; + p = write_pair_number(p, "max_udp_payload_size", + params->max_udp_payload_size); + *p++ = ','; + p = write_pair_number(p, "ack_delay_exponent", params->ack_delay_exponent); + *p++ = ','; + p = write_pair_duration(p, "max_ack_delay", params->max_ack_delay); + *p++ = ','; + p = write_pair_number(p, "active_connection_id_limit", + params->active_connection_id_limit); + *p++ = ','; + p = write_pair_number(p, "initial_max_data", params->initial_max_data); + *p++ = ','; + p = write_pair_number(p, "initial_max_stream_data_bidi_local", + params->initial_max_stream_data_bidi_local); + *p++ = ','; + p = write_pair_number(p, "initial_max_stream_data_bidi_remote", + params->initial_max_stream_data_bidi_remote); + *p++ = ','; + p = write_pair_number(p, "initial_max_stream_data_uni", + params->initial_max_stream_data_uni); + *p++ = ','; + p = write_pair_number(p, "initial_max_streams_bidi", + params->initial_max_streams_bidi); + *p++ = ','; + p = write_pair_number(p, "initial_max_streams_uni", + params->initial_max_streams_uni); + if (params->preferred_address_present) { + *p++ = ','; + paddr = ¶ms->preferred_address; + p = write_string(p, "preferred_address"); + *p++ = ':'; + *p++ = '{'; + + if (paddr->ipv4_present) { + sa_in = &paddr->ipv4; + + p = write_pair_hex(p, "ip_v4", (const uint8_t *)&sa_in->sin_addr, + sizeof(sa_in->sin_addr)); + *p++ = ','; + p = write_pair_number(p, "port_v4", ngtcp2_ntohs(sa_in->sin_port)); + *p++ = ','; + } + + if (paddr->ipv6_present) { + sa_in6 = &paddr->ipv6; + + p = write_pair_hex(p, "ip_v6", (const uint8_t *)&sa_in6->sin6_addr, + sizeof(sa_in6->sin6_addr)); + *p++ = ','; + p = write_pair_number(p, "port_v6", ngtcp2_ntohs(sa_in6->sin6_port)); + *p++ = ','; + } + + p = write_pair_cid(p, "connection_id", &paddr->cid); + p = write_verbatim(p, ",\"stateless_reset_token\":{"); + p = write_pair_hex(p, "data", paddr->stateless_reset_token, + sizeof(paddr->stateless_reset_token)); + *p++ = '}'; + *p++ = '}'; + } + *p++ = ','; + p = write_pair_number(p, "max_datagram_frame_size", + params->max_datagram_frame_size); + *p++ = ','; + p = write_pair_bool(p, "grease_quic_bit", params->grease_quic_bit); + p = write_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +void ngtcp2_qlog_metrics_updated(ngtcp2_qlog *qlog, + const ngtcp2_conn_stat *cstat) { + uint8_t buf[1024]; + uint8_t *p = buf; + + if (!qlog->write) { + return; + } + + *p++ = '\x1e'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim(p, ",\"name\":\"recovery:metrics_updated\",\"data\":{"); + + if (cstat->min_rtt != UINT64_MAX) { + p = write_pair_duration(p, "min_rtt", cstat->min_rtt); + *p++ = ','; + } + p = write_pair_duration(p, "smoothed_rtt", cstat->smoothed_rtt); + *p++ = ','; + p = write_pair_duration(p, "latest_rtt", cstat->latest_rtt); + *p++ = ','; + p = write_pair_duration(p, "rtt_variance", cstat->rttvar); + *p++ = ','; + p = write_pair_number(p, "pto_count", cstat->pto_count); + *p++ = ','; + p = write_pair_number(p, "congestion_window", cstat->cwnd); + *p++ = ','; + p = write_pair_number(p, "bytes_in_flight", cstat->bytes_in_flight); + if (cstat->ssthresh != UINT64_MAX) { + *p++ = ','; + p = write_pair_number(p, "ssthresh", cstat->ssthresh); + } + + p = write_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +void ngtcp2_qlog_pkt_lost(ngtcp2_qlog *qlog, ngtcp2_rtb_entry *ent) { + uint8_t buf[256]; + uint8_t *p = buf; + ngtcp2_pkt_hd hd = {0}; + + if (!qlog->write) { + return; + } + + *p++ = '\x1e'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim( + p, ",\"name\":\"recovery:packet_lost\",\"data\":{\"header\":"); + + hd.type = ent->hd.type; + hd.flags = ent->hd.flags; + hd.pkt_num = ent->hd.pkt_num; + + p = write_pkt_hd(p, &hd); + p = write_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +void ngtcp2_qlog_retry_pkt_received(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + const ngtcp2_pkt_retry *retry) { + uint8_t rawbuf[1024]; + ngtcp2_buf buf; + + if (!qlog->write) { + return; + } + + ngtcp2_buf_init(&buf, rawbuf, sizeof(rawbuf)); + + *buf.last++ = '\x1e'; + *buf.last++ = '{'; + buf.last = qlog_write_time(qlog, buf.last); + buf.last = write_verbatim( + buf.last, + ",\"name\":\"transport:packet_received\",\"data\":{\"header\":"); + + if (ngtcp2_buf_left(&buf) < + NGTCP2_QLOG_PKT_HD_OVERHEAD + hd->token.len * 2 + + sizeof(",\"retry_token\":{\"data\":\"\"}}}\n") - 1 + + retry->token.len * 2) { + return; + } + + buf.last = write_pkt_hd(buf.last, hd); + buf.last = write_verbatim(buf.last, ",\"retry_token\":{"); + buf.last = + write_pair_hex(buf.last, "data", retry->token.base, retry->token.len); + buf.last = write_verbatim(buf.last, "}}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf.pos, + ngtcp2_buf_len(&buf)); +} + +void ngtcp2_qlog_stateless_reset_pkt_received( + ngtcp2_qlog *qlog, const ngtcp2_pkt_stateless_reset *sr) { + uint8_t buf[256]; + uint8_t *p = buf; + ngtcp2_pkt_hd hd = {0}; + + if (!qlog->write) { + return; + } + + hd.type = NGTCP2_PKT_STATELESS_RESET; + + *p++ = '\x1e'; + *p++ = '{'; + p = qlog_write_time(qlog, p); + p = write_verbatim( + p, ",\"name\":\"transport:packet_received\",\"data\":{\"header\":"); + p = write_pkt_hd(p, &hd); + *p++ = ','; + p = write_pair_hex(p, "stateless_reset_token", sr->stateless_reset_token, + NGTCP2_STATELESS_RESET_TOKENLEN); + p = write_verbatim(p, "}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf, + (size_t)(p - buf)); +} + +void ngtcp2_qlog_version_negotiation_pkt_received(ngtcp2_qlog *qlog, + const ngtcp2_pkt_hd *hd, + const uint32_t *sv, + size_t nsv) { + uint8_t rawbuf[512]; + ngtcp2_buf buf; + size_t i; + uint32_t v; + + if (!qlog->write) { + return; + } + + ngtcp2_buf_init(&buf, rawbuf, sizeof(rawbuf)); + + *buf.last++ = '\x1e'; + *buf.last++ = '{'; + buf.last = qlog_write_time(qlog, buf.last); + buf.last = write_verbatim( + buf.last, + ",\"name\":\"transport:packet_received\",\"data\":{\"header\":"); + buf.last = write_pkt_hd(buf.last, hd); + buf.last = write_verbatim(buf.last, ",\"supported_versions\":["); + + if (nsv) { + if (ngtcp2_buf_left(&buf) < + (sizeof("\"xxxxxxxx\",") - 1) * nsv - 1 + sizeof("]}}\n") - 1) { + return; + } + + v = ngtcp2_htonl(sv[0]); + buf.last = write_hex(buf.last, (const uint8_t *)&v, sizeof(v)); + + for (i = 1; i < nsv; ++i) { + *buf.last++ = ','; + v = ngtcp2_htonl(sv[i]); + buf.last = write_hex(buf.last, (const uint8_t *)&v, sizeof(v)); + } + } + + buf.last = write_verbatim(buf.last, "]}}\n"); + + qlog->write(qlog->user_data, NGTCP2_QLOG_WRITE_FLAG_NONE, buf.pos, + ngtcp2_buf_len(&buf)); +} diff --git a/lib/ngtcp2_qlog.h b/lib/ngtcp2_qlog.h new file mode 100644 index 0000000..b9107c0 --- /dev/null +++ b/lib/ngtcp2_qlog.h @@ -0,0 +1,161 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_QLOG_H +#define NGTCP2_QLOG_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pkt.h" +#include "ngtcp2_cc.h" +#include "ngtcp2_buf.h" +#include "ngtcp2_rtb.h" + +/* NGTCP2_QLOG_BUFLEN is the length of heap allocated buffer for + qlog. */ +#define NGTCP2_QLOG_BUFLEN 4096 + +typedef enum ngtcp2_qlog_side { + NGTCP2_QLOG_SIDE_LOCAL, + NGTCP2_QLOG_SIDE_REMOTE, +} ngtcp2_qlog_side; + +typedef struct ngtcp2_qlog { + /* write is a callback function to write qlog. */ + ngtcp2_qlog_write write; + /* ts is the initial timestamp */ + ngtcp2_tstamp ts; + /* last_ts is the timestamp observed last time. */ + ngtcp2_tstamp last_ts; + /* buf is a heap allocated buffer to write exclusively + packet_received and packet_sent. */ + ngtcp2_buf buf; + /* user_data is an opaque pointer which is passed to write + callback. */ + void *user_data; +} ngtcp2_qlog; + +/* + * ngtcp2_qlog_init initializes |qlog|. + */ +void ngtcp2_qlog_init(ngtcp2_qlog *qlog, ngtcp2_qlog_write write, + ngtcp2_tstamp ts, void *user_data); + +/* + * ngtcp2_qlog_start writes qlog preamble. + */ +void ngtcp2_qlog_start(ngtcp2_qlog *qlog, const ngtcp2_cid *odcid, int server); + +/* + * ngtcp2_qlog_end writes closing part of qlog. + */ +void ngtcp2_qlog_end(ngtcp2_qlog *qlog); + +/* + * ngtcp2_qlog_write_frame writes |fr| to qlog->buf. + * ngtcp2_qlog_pkt_received_start or ngtcp2_qlog_pkt_sent_start must + * be called before calling this function. + */ +void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr); + +/* + * ngtcp2_qlog_pkt_received_start starts to write packet_received + * event. It initializes qlog->buf. It writes qlog to qlog->buf. + * ngtcp2_qlog_pkt_received_end will flush the content of qlog->buf to + * write callback. + */ +void ngtcp2_qlog_pkt_received_start(ngtcp2_qlog *qlog); + +/* + * ngtcp2_qlog_pkt_received_end ends packet_received event and sends + * the content of qlog->buf to qlog->write callback. + */ +void ngtcp2_qlog_pkt_received_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen); + +/* + * ngtcp2_qlog_pkt_sent_start starts to write packet_sent event. It + * initializes qlog->buf. It writes qlog to qlog->buf. + * ngtcp2_qlog_pkt_sent_end will flush the content of qlog->buf to + * write callback. + */ +void ngtcp2_qlog_pkt_sent_start(ngtcp2_qlog *qlog); + +/* + * ngtcp2_qlog_pkt_sent_end ends packet_sent event and sends the + * content of qlog->buf to qlog->write callback. + */ +void ngtcp2_qlog_pkt_sent_end(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + size_t pktlen); + +/* + * ngtcp2_qlog_parameters_set_transport_params writes |params| to qlog + * as parameters_set event. |server| is nonzero if the local endpoint + * is server. If |local| is nonzero, it is "owner" field becomes + * "local", otherwise "remote". + */ +void ngtcp2_qlog_parameters_set_transport_params( + ngtcp2_qlog *qlog, const ngtcp2_transport_params *params, int server, + ngtcp2_qlog_side side); + +/* + * ngtcp2_qlog_metrics_updated writes metrics_updated event of + * recovery category. + */ +void ngtcp2_qlog_metrics_updated(ngtcp2_qlog *qlog, + const ngtcp2_conn_stat *cstat); + +/* + * ngtcp2_qlog_pkt_lost writes packet_lost event. + */ +void ngtcp2_qlog_pkt_lost(ngtcp2_qlog *qlog, ngtcp2_rtb_entry *ent); + +/* + * ngtcp2_qlog_retry_pkt_received writes packet_received event for a + * received Retry packet. + */ +void ngtcp2_qlog_retry_pkt_received(ngtcp2_qlog *qlog, const ngtcp2_pkt_hd *hd, + const ngtcp2_pkt_retry *retry); + +/* + * ngtcp2_qlog_stateless_reset_pkt_received writes packet_received + * event for a received Stateless Reset packet. + */ +void ngtcp2_qlog_stateless_reset_pkt_received( + ngtcp2_qlog *qlog, const ngtcp2_pkt_stateless_reset *sr); + +/* + * ngtcp2_qlog_version_negotiation_pkt_received writes packet_received + * event for a received Version Negotiation packet. + */ +void ngtcp2_qlog_version_negotiation_pkt_received(ngtcp2_qlog *qlog, + const ngtcp2_pkt_hd *hd, + const uint32_t *sv, + size_t nsv); + +#endif /* NGTCP2_QLOG_H */ diff --git a/lib/ngtcp2_range.c b/lib/ngtcp2_range.c new file mode 100644 index 0000000..9379496 --- /dev/null +++ b/lib/ngtcp2_range.c @@ -0,0 +1,61 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_range.h" +#include "ngtcp2_macro.h" + +void ngtcp2_range_init(ngtcp2_range *r, uint64_t begin, uint64_t end) { + r->begin = begin; + r->end = end; +} + +ngtcp2_range ngtcp2_range_intersect(const ngtcp2_range *a, + const ngtcp2_range *b) { + ngtcp2_range r = {0, 0}; + uint64_t begin = ngtcp2_max(a->begin, b->begin); + uint64_t end = ngtcp2_min(a->end, b->end); + if (begin < end) { + ngtcp2_range_init(&r, begin, end); + } + return r; +} + +uint64_t ngtcp2_range_len(const ngtcp2_range *r) { return r->end - r->begin; } + +int ngtcp2_range_eq(const ngtcp2_range *a, const ngtcp2_range *b) { + return a->begin == b->begin && a->end == b->end; +} + +void ngtcp2_range_cut(ngtcp2_range *left, ngtcp2_range *right, + const ngtcp2_range *a, const ngtcp2_range *b) { + /* Assume that b is included in a */ + left->begin = a->begin; + left->end = b->begin; + right->begin = b->end; + right->end = a->end; +} + +int ngtcp2_range_not_after(const ngtcp2_range *a, const ngtcp2_range *b) { + return a->end <= b->end; +} diff --git a/lib/ngtcp2_range.h b/lib/ngtcp2_range.h new file mode 100644 index 0000000..a776c4e --- /dev/null +++ b/lib/ngtcp2_range.h @@ -0,0 +1,80 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_RANGE_H +#define NGTCP2_RANGE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* + * ngtcp2_range represents half-closed range [begin, end). + */ +typedef struct ngtcp2_range { + uint64_t begin; + uint64_t end; +} ngtcp2_range; + +/* + * ngtcp2_range_init initializes |r| with the range [|begin|, |end|). + */ +void ngtcp2_range_init(ngtcp2_range *r, uint64_t begin, uint64_t end); + +/* + * ngtcp2_range_intersect returns the intersection of |a| and |b|. If + * they do not overlap, it returns empty range. + */ +ngtcp2_range ngtcp2_range_intersect(const ngtcp2_range *a, + const ngtcp2_range *b); + +/* + * ngtcp2_range_len returns the length of |r|. + */ +uint64_t ngtcp2_range_len(const ngtcp2_range *r); + +/* + * ngtcp2_range_eq returns nonzero if |a| equals |b|, such that + * a->begin == b->begin, and a->end == b->end hold. + */ +int ngtcp2_range_eq(const ngtcp2_range *a, const ngtcp2_range *b); + +/* + * ngtcp2_range_cut returns the left and right range after removing + * |b| from |a|. This function assumes that |a| completely includes + * |b|. In other words, a->begin <= b->begin and b->end <= a->end + * hold. + */ +void ngtcp2_range_cut(ngtcp2_range *left, ngtcp2_range *right, + const ngtcp2_range *a, const ngtcp2_range *b); + +/* + * ngtcp2_range_not_after returns nonzero if the right edge of |a| + * does not go beyond of the right edge of |b|. + */ +int ngtcp2_range_not_after(const ngtcp2_range *a, const ngtcp2_range *b); + +#endif /* NGTCP2_RANGE_H */ diff --git a/lib/ngtcp2_rcvry.h b/lib/ngtcp2_rcvry.h new file mode 100644 index 0000000..4cb4088 --- /dev/null +++ b/lib/ngtcp2_rcvry.h @@ -0,0 +1,40 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_RCVRY_H +#define NGTCP2_RCVRY_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +/* NGTCP2_PKT_THRESHOLD is kPacketThreshold described in RFC 9002. */ +#define NGTCP2_PKT_THRESHOLD 3 + +/* NGTCP2_GRANULARITY is kGranularity described in RFC 9002. */ +#define NGTCP2_GRANULARITY NGTCP2_MILLISECONDS + +#endif /* NGTCP2_RCVRY_H */ diff --git a/lib/ngtcp2_ringbuf.c b/lib/ngtcp2_ringbuf.c new file mode 100644 index 0000000..a6b3f73 --- /dev/null +++ b/lib/ngtcp2_ringbuf.c @@ -0,0 +1,120 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_ringbuf.h" + +#include <assert.h> +#ifdef WIN32 +# include <intrin.h> +#endif + +#include "ngtcp2_macro.h" + +#if defined(_MSC_VER) && defined(_M_ARM64) +unsigned int __popcnt(unsigned int x) { + unsigned int c = 0; + for (; x; ++c) { + x &= x - 1; + } + return c; +} +#endif + +int ngtcp2_ringbuf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size, + const ngtcp2_mem *mem) { + uint8_t *buf = ngtcp2_mem_malloc(mem, nmemb * size); + if (buf == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_ringbuf_buf_init(rb, nmemb, size, buf, mem); + + return 0; +} + +void ngtcp2_ringbuf_buf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size, + uint8_t *buf, const ngtcp2_mem *mem) { +#ifdef WIN32 + assert(1 == __popcnt((unsigned int)nmemb)); +#else + assert(1 == __builtin_popcount((unsigned int)nmemb)); +#endif + + rb->buf = buf; + rb->mem = mem; + rb->nmemb = nmemb; + rb->size = size; + rb->first = 0; + rb->len = 0; +} + +void ngtcp2_ringbuf_free(ngtcp2_ringbuf *rb) { + if (rb == NULL) { + return; + } + + ngtcp2_mem_free(rb->mem, rb->buf); +} + +void *ngtcp2_ringbuf_push_front(ngtcp2_ringbuf *rb) { + rb->first = (rb->first - 1) & (rb->nmemb - 1); + rb->len = ngtcp2_min(rb->nmemb, rb->len + 1); + + return (void *)&rb->buf[rb->first * rb->size]; +} + +void *ngtcp2_ringbuf_push_back(ngtcp2_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 ngtcp2_ringbuf_pop_front(ngtcp2_ringbuf *rb) { + rb->first = (rb->first + 1) & (rb->nmemb - 1); + --rb->len; +} + +void ngtcp2_ringbuf_pop_back(ngtcp2_ringbuf *rb) { + assert(rb->len); + --rb->len; +} + +void ngtcp2_ringbuf_resize(ngtcp2_ringbuf *rb, size_t len) { + assert(len <= rb->nmemb); + rb->len = len; +} + +void *ngtcp2_ringbuf_get(ngtcp2_ringbuf *rb, size_t offset) { + assert(offset < rb->len); + offset = (rb->first + offset) & (rb->nmemb - 1); + return &rb->buf[offset * rb->size]; +} + +int ngtcp2_ringbuf_full(ngtcp2_ringbuf *rb) { return rb->len == rb->nmemb; } diff --git a/lib/ngtcp2_ringbuf.h b/lib/ngtcp2_ringbuf.h new file mode 100644 index 0000000..16635c9 --- /dev/null +++ b/lib/ngtcp2_ringbuf.h @@ -0,0 +1,132 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_RINGBUF_H +#define NGTCP2_RINGBUF_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" + +typedef struct ngtcp2_ringbuf { + /* buf points to the underlying buffer. */ + uint8_t *buf; + const ngtcp2_mem *mem; + /* 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; +} ngtcp2_ringbuf; + +/* + * ngtcp2_ringbuf_init initializes |rb|. |nmemb| is the number of + * elements that can be stored in this buffer. |size| is the size of + * each element. |size| must be power of 2. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_ringbuf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size, + const ngtcp2_mem *mem); + +/* + * ngtcp2_ringbuf_buf_init initializes |rb| with given buffer and + * size. + */ +void ngtcp2_ringbuf_buf_init(ngtcp2_ringbuf *rb, size_t nmemb, size_t size, + uint8_t *buf, const ngtcp2_mem *mem); + +/* + * ngtcp2_ringbuf_free frees resources allocated for |rb|. This + * function does not free the memory pointed by |rb|. + */ +void ngtcp2_ringbuf_free(ngtcp2_ringbuf *rb); + +/* ngtcp2_ringbuf_push_front moves the offset to the first element in + the buffer backward, and returns the pointer to the element. + Caller can store data to the buffer pointed by the returned + pointer. If this action exceeds the capacity of the ring buffer, + the last element is silently overwritten, and rb->len remains + unchanged. */ +void *ngtcp2_ringbuf_push_front(ngtcp2_ringbuf *rb); + +/* ngtcp2_ringbuf_push_back moves the offset to the last element in + the buffer forward, and returns the pointer to the element. Caller + can store data to the buffer pointed by the returned pointer. If + this action exceeds the capacity of the ring buffer, the first + element is silently overwritten, and rb->len remains unchanged. */ +void *ngtcp2_ringbuf_push_back(ngtcp2_ringbuf *rb); + +/* + * ngtcp2_ringbuf_pop_front removes first element in |rb|. + */ +void ngtcp2_ringbuf_pop_front(ngtcp2_ringbuf *rb); + +/* + * ngtcp2_ringbuf_pop_back removes the last element in |rb|. + */ +void ngtcp2_ringbuf_pop_back(ngtcp2_ringbuf *rb); + +/* ngtcp2_ringbuf_resize changes the number of elements stored. This + does not change the capacity of the underlying buffer. */ +void ngtcp2_ringbuf_resize(ngtcp2_ringbuf *rb, size_t len); + +/* ngtcp2_ringbuf_get returns the pointer to the element at + |offset|. */ +void *ngtcp2_ringbuf_get(ngtcp2_ringbuf *rb, size_t offset); + +/* ngtcp2_ringbuf_len returns the number of elements stored. */ +#define ngtcp2_ringbuf_len(RB) ((RB)->len) + +/* ngtcp2_ringbuf_full returns nonzero if |rb| is full. */ +int ngtcp2_ringbuf_full(ngtcp2_ringbuf *rb); + +/* ngtcp2_static_ringbuf_def defines ngtcp2_ringbuf struct wrapper + which uses a statically allocated buffer that is suitable for a + usage that does not change buffer size with ngtcp2_ringbuf_resize. + ngtcp2_ringbuf_free should never be called for rb field. */ +#define ngtcp2_static_ringbuf_def(NAME, NMEMB, SIZE) \ + typedef struct ngtcp2_static_ringbuf_##NAME { \ + ngtcp2_ringbuf rb; \ + uint8_t buf[(NMEMB) * (SIZE)]; \ + } ngtcp2_static_ringbuf_##NAME; \ + \ + static inline void ngtcp2_static_ringbuf_##NAME##_init( \ + ngtcp2_static_ringbuf_##NAME *srb) { \ + ngtcp2_ringbuf_buf_init(&srb->rb, (NMEMB), (SIZE), srb->buf, NULL); \ + } + +#endif /* NGTCP2_RINGBUF_H */ diff --git a/lib/ngtcp2_rob.c b/lib/ngtcp2_rob.c new file mode 100644 index 0000000..9c3d75d --- /dev/null +++ b/lib/ngtcp2_rob.c @@ -0,0 +1,319 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_rob.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_macro.h" + +int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end, + const ngtcp2_mem *mem) { + *pg = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_rob_gap)); + if (*pg == NULL) { + return NGTCP2_ERR_NOMEM; + } + + (*pg)->range.begin = begin; + (*pg)->range.end = end; + + return 0; +} + +void ngtcp2_rob_gap_del(ngtcp2_rob_gap *g, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, g); +} + +int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk, + const ngtcp2_mem *mem) { + *pd = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_rob_data) + chunk); + if (*pd == NULL) { + return NGTCP2_ERR_NOMEM; + } + + (*pd)->range.begin = offset; + (*pd)->range.end = offset + chunk; + (*pd)->begin = (uint8_t *)(*pd) + sizeof(ngtcp2_rob_data); + (*pd)->end = (*pd)->begin + chunk; + + return 0; +} + +void ngtcp2_rob_data_del(ngtcp2_rob_data *d, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, d); +} + +int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, const ngtcp2_mem *mem) { + int rv; + ngtcp2_rob_gap *g; + + ngtcp2_ksl_init(&rob->gapksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), + mem); + + rv = ngtcp2_rob_gap_new(&g, 0, UINT64_MAX, mem); + if (rv != 0) { + goto fail_rob_gap_new; + } + + rv = ngtcp2_ksl_insert(&rob->gapksl, NULL, &g->range, g); + if (rv != 0) { + goto fail_gapksl_ksl_insert; + } + + ngtcp2_ksl_init(&rob->dataksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), + mem); + + rob->chunk = chunk; + rob->mem = mem; + + return 0; + +fail_gapksl_ksl_insert: + ngtcp2_rob_gap_del(g, mem); +fail_rob_gap_new: + ngtcp2_ksl_free(&rob->gapksl); + return rv; +} + +void ngtcp2_rob_free(ngtcp2_rob *rob) { + ngtcp2_ksl_it it; + + if (rob == NULL) { + return; + } + + for (it = ngtcp2_ksl_begin(&rob->dataksl); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_rob_data_del(ngtcp2_ksl_it_get(&it), rob->mem); + } + + for (it = ngtcp2_ksl_begin(&rob->gapksl); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_rob_gap_del(ngtcp2_ksl_it_get(&it), rob->mem); + } + + ngtcp2_ksl_free(&rob->dataksl); + ngtcp2_ksl_free(&rob->gapksl); +} + +static int rob_write_data(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, + size_t len) { + size_t n; + int rv; + ngtcp2_rob_data *d; + ngtcp2_range range = {offset, offset + len}; + ngtcp2_ksl_it it; + + for (it = ngtcp2_ksl_lower_bound_compar(&rob->dataksl, &range, + ngtcp2_ksl_range_exclusive_compar); + len; ngtcp2_ksl_it_next(&it)) { + if (ngtcp2_ksl_it_end(&it)) { + d = NULL; + } else { + d = ngtcp2_ksl_it_get(&it); + } + + if (d == NULL || offset < d->range.begin) { + rv = ngtcp2_rob_data_new(&d, (offset / rob->chunk) * rob->chunk, + rob->chunk, rob->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_ksl_insert(&rob->dataksl, &it, &d->range, d); + if (rv != 0) { + ngtcp2_rob_data_del(d, rob->mem); + return rv; + } + } + + n = (size_t)ngtcp2_min((uint64_t)len, d->range.begin + rob->chunk - offset); + memcpy(d->begin + (offset - d->range.begin), data, n); + offset += n; + data += n; + len -= n; + } + + return 0; +} + +int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, + size_t datalen) { + int rv; + ngtcp2_rob_gap *g; + ngtcp2_range m, l, r, q = {offset, offset + datalen}; + ngtcp2_ksl_it it; + + it = ngtcp2_ksl_lower_bound_compar(&rob->gapksl, &q, + ngtcp2_ksl_range_exclusive_compar); + + for (; !ngtcp2_ksl_it_end(&it);) { + g = ngtcp2_ksl_it_get(&it); + + m = ngtcp2_range_intersect(&q, &g->range); + if (!ngtcp2_range_len(&m)) { + break; + } + if (ngtcp2_range_eq(&g->range, &m)) { + ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g->range); + ngtcp2_rob_gap_del(g, rob->mem); + rv = rob_write_data(rob, m.begin, data + (m.begin - offset), + (size_t)ngtcp2_range_len(&m)); + if (rv != 0) { + return rv; + } + + continue; + } + ngtcp2_range_cut(&l, &r, &g->range, &m); + if (ngtcp2_range_len(&l)) { + ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &l); + g->range = l; + + if (ngtcp2_range_len(&r)) { + ngtcp2_rob_gap *ng; + rv = ngtcp2_rob_gap_new(&ng, r.begin, r.end, rob->mem); + if (rv != 0) { + return rv; + } + rv = ngtcp2_ksl_insert(&rob->gapksl, &it, &ng->range, ng); + if (rv != 0) { + ngtcp2_rob_gap_del(ng, rob->mem); + return rv; + } + } + } else if (ngtcp2_range_len(&r)) { + ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &r); + g->range = r; + } + rv = rob_write_data(rob, m.begin, data + (m.begin - offset), + (size_t)ngtcp2_range_len(&m)); + if (rv != 0) { + return rv; + } + ngtcp2_ksl_it_next(&it); + } + return 0; +} + +int ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset) { + ngtcp2_rob_gap *g; + ngtcp2_rob_data *d; + ngtcp2_ksl_it it; + + it = ngtcp2_ksl_begin(&rob->gapksl); + + for (; !ngtcp2_ksl_it_end(&it);) { + g = ngtcp2_ksl_it_get(&it); + if (offset <= g->range.begin) { + break; + } + if (offset < g->range.end) { + ngtcp2_range r = {offset, g->range.end}; + ngtcp2_ksl_update_key(&rob->gapksl, &g->range, &r); + g->range.begin = offset; + break; + } + ngtcp2_ksl_remove_hint(&rob->gapksl, &it, &it, &g->range); + ngtcp2_rob_gap_del(g, rob->mem); + } + + it = ngtcp2_ksl_begin(&rob->dataksl); + + for (; !ngtcp2_ksl_it_end(&it);) { + d = ngtcp2_ksl_it_get(&it); + if (offset < d->range.begin + rob->chunk) { + return 0; + } + ngtcp2_ksl_remove_hint(&rob->dataksl, &it, &it, &d->range); + ngtcp2_rob_data_del(d, rob->mem); + } + + return 0; +} + +size_t ngtcp2_rob_data_at(ngtcp2_rob *rob, const uint8_t **pdest, + uint64_t offset) { + ngtcp2_rob_gap *g; + ngtcp2_rob_data *d; + ngtcp2_ksl_it it; + + it = ngtcp2_ksl_begin(&rob->gapksl); + if (ngtcp2_ksl_it_end(&it)) { + return 0; + } + + g = ngtcp2_ksl_it_get(&it); + + if (g->range.begin <= offset) { + return 0; + } + + it = ngtcp2_ksl_begin(&rob->dataksl); + d = ngtcp2_ksl_it_get(&it); + + assert(d); + assert(d->range.begin <= offset); + assert(offset < d->range.begin + rob->chunk); + + *pdest = d->begin + (offset - d->range.begin); + + return (size_t)(ngtcp2_min(g->range.begin, d->range.begin + rob->chunk) - + offset); +} + +void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len) { + ngtcp2_ksl_it it; + ngtcp2_rob_data *d; + + it = ngtcp2_ksl_begin(&rob->dataksl); + d = ngtcp2_ksl_it_get(&it); + + assert(d); + + if (offset + len < d->range.begin + rob->chunk) { + return; + } + + ngtcp2_ksl_remove_hint(&rob->dataksl, NULL, &it, &d->range); + ngtcp2_rob_data_del(d, rob->mem); +} + +uint64_t ngtcp2_rob_first_gap_offset(ngtcp2_rob *rob) { + ngtcp2_ksl_it it = ngtcp2_ksl_begin(&rob->gapksl); + ngtcp2_rob_gap *g; + + if (ngtcp2_ksl_it_end(&it)) { + return UINT64_MAX; + } + + g = ngtcp2_ksl_it_get(&it); + + return g->range.begin; +} + +int ngtcp2_rob_data_buffered(ngtcp2_rob *rob) { + return ngtcp2_ksl_len(&rob->dataksl) != 0; +} diff --git a/lib/ngtcp2_rob.h b/lib/ngtcp2_rob.h new file mode 100644 index 0000000..c7688df --- /dev/null +++ b/lib/ngtcp2_rob.h @@ -0,0 +1,197 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_ROB_H +#define NGTCP2_ROB_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" +#include "ngtcp2_range.h" +#include "ngtcp2_ksl.h" + +/* + * ngtcp2_rob_gap represents the gap, which is the range of stream + * data that is not received yet. + */ +typedef struct ngtcp2_rob_gap { + /* range is the range of this gap. */ + ngtcp2_range range; +} ngtcp2_rob_gap; + +/* + * ngtcp2_rob_gap_new allocates new ngtcp2_rob_gap object, and assigns + * its pointer to |*pg|. The caller should call ngtcp2_rob_gap_del to + * delete it when it is no longer used. The range of the gap is + * [begin, end). |mem| is custom memory allocator to allocate memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_rob_gap_new(ngtcp2_rob_gap **pg, uint64_t begin, uint64_t end, + const ngtcp2_mem *mem); + +/* + * ngtcp2_rob_gap_del deallocates |g|. It deallocates the memory + * pointed by |g| it self. |mem| is custom memory allocator to + * deallocate memory. + */ +void ngtcp2_rob_gap_del(ngtcp2_rob_gap *g, const ngtcp2_mem *mem); + +/* + * ngtcp2_rob_data holds the buffered stream data. + */ +typedef struct ngtcp2_rob_data { + /* range is the range of this gap. */ + ngtcp2_range range; + /* begin points to the buffer. */ + uint8_t *begin; + /* end points to the one beyond of the last byte of the buffer */ + uint8_t *end; +} ngtcp2_rob_data; + +/* + * ngtcp2_rob_data_new allocates new ngtcp2_rob_data object, and + * assigns its pointer to |*pd|. The caller should call + * ngtcp2_rob_data_del to delete it when it is no longer used. + * |offset| is the stream offset of the first byte of this data. + * |chunk| is the size of the buffer. |offset| must be multiple of + * |chunk|. |mem| is custom memory allocator to allocate memory. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_rob_data_new(ngtcp2_rob_data **pd, uint64_t offset, size_t chunk, + const ngtcp2_mem *mem); + +/* + * ngtcp2_rob_data_del deallocates |d|. It deallocates the memory + * pointed by |d| itself. |mem| is custom memory allocator to + * deallocate memory. + */ +void ngtcp2_rob_data_del(ngtcp2_rob_data *d, const ngtcp2_mem *mem); + +/* + * ngtcp2_rob is the reorder buffer which reassembles stream data + * received in out of order. + */ +typedef struct ngtcp2_rob { + /* gapksl maintains the range of offset which is not received + yet. Initially, its range is [0, UINT64_MAX). */ + ngtcp2_ksl gapksl; + /* dataksl maintains the list of buffers which store received data + ordered by stream offset. */ + ngtcp2_ksl dataksl; + /* mem is custom memory allocator */ + const ngtcp2_mem *mem; + /* chunk is the size of each buffer in data field */ + size_t chunk; +} ngtcp2_rob; + +/* + * ngtcp2_rob_init initializes |rob|. |chunk| is the size of buffer + * per chunk. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_rob_init(ngtcp2_rob *rob, size_t chunk, const ngtcp2_mem *mem); + +/* + * ngtcp2_rob_free frees resources allocated for |rob|. + */ +void ngtcp2_rob_free(ngtcp2_rob *rob); + +/* + * ngtcp2_rob_push adds new data of length |datalen| at the stream + * offset |offset|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_rob_push(ngtcp2_rob *rob, uint64_t offset, const uint8_t *data, + size_t datalen); + +/* + * ngtcp2_rob_remove_prefix removes gap up to |offset|, exclusive. It + * also removes data buffer if it is completely included in |offset|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_rob_remove_prefix(ngtcp2_rob *rob, uint64_t offset); + +/* + * ngtcp2_rob_data_at stores the pointer to the buffer of stream + * offset |offset| to |*pdest| if it is available, and returns the + * valid length of available data. If no data is available, it + * returns 0. + */ +size_t ngtcp2_rob_data_at(ngtcp2_rob *rob, const uint8_t **pdest, + uint64_t offset); + +/* + * ngtcp2_rob_pop clears data at stream offset |offset| of length + * |len|. + * + * |offset| must be the offset given in ngtcp2_rob_data_at. |len| + * must be the return value of ngtcp2_rob_data_at when |offset| is + * passed. + * + * Caller should call this function from offset 0 in non-decreasing + * order. + */ +void ngtcp2_rob_pop(ngtcp2_rob *rob, uint64_t offset, size_t len); + +/* + * ngtcp2_rob_first_gap_offset returns the offset to the first gap. + * If there is no gap, it returns UINT64_MAX. + */ +uint64_t ngtcp2_rob_first_gap_offset(ngtcp2_rob *rob); + +/* + * ngtcp2_rob_data_buffered returns nonzero if any data is buffered. + */ +int ngtcp2_rob_data_buffered(ngtcp2_rob *rob); + +#endif /* NGTCP2_ROB_H */ diff --git a/lib/ngtcp2_rst.c b/lib/ngtcp2_rst.c new file mode 100644 index 0000000..7b50f98 --- /dev/null +++ b/lib/ngtcp2_rst.c @@ -0,0 +1,137 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_rst.h" + +#include <assert.h> + +#include "ngtcp2_rtb.h" +#include "ngtcp2_cc.h" +#include "ngtcp2_macro.h" + +void ngtcp2_rs_init(ngtcp2_rs *rs) { + rs->interval = UINT64_MAX; + rs->delivered = 0; + rs->prior_delivered = 0; + rs->prior_ts = 0; + rs->tx_in_flight = 0; + rs->lost = 0; + rs->prior_lost = 0; + rs->send_elapsed = 0; + rs->ack_elapsed = 0; + rs->is_app_limited = 0; +} + +void ngtcp2_rst_init(ngtcp2_rst *rst) { + ngtcp2_rs_init(&rst->rs); + ngtcp2_window_filter_init(&rst->wf, 12); + rst->delivered = 0; + rst->delivered_ts = 0; + rst->first_sent_ts = 0; + rst->app_limited = 0; + rst->next_round_delivered = 0; + rst->round_count = 0; + rst->is_cwnd_limited = 0; + rst->lost = 0; +} + +void ngtcp2_rst_on_pkt_sent(ngtcp2_rst *rst, ngtcp2_rtb_entry *ent, + const ngtcp2_conn_stat *cstat) { + if (cstat->bytes_in_flight == 0) { + rst->first_sent_ts = rst->delivered_ts = ent->ts; + } + ent->rst.first_sent_ts = rst->first_sent_ts; + ent->rst.delivered_ts = rst->delivered_ts; + ent->rst.delivered = rst->delivered; + ent->rst.is_app_limited = rst->app_limited != 0; + ent->rst.tx_in_flight = cstat->bytes_in_flight + ent->pktlen; + ent->rst.lost = rst->lost; +} + +int ngtcp2_rst_on_ack_recv(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat, + uint64_t pkt_delivered) { + ngtcp2_rs *rs = &rst->rs; + uint64_t rate; + + if (rst->app_limited && rst->delivered > rst->app_limited) { + rst->app_limited = 0; + } + + if (pkt_delivered >= rst->next_round_delivered) { + rst->next_round_delivered = pkt_delivered; + ++rst->round_count; + } + + if (rs->prior_ts == 0) { + return 0; + } + + rs->interval = ngtcp2_max(rs->send_elapsed, rs->ack_elapsed); + + rs->delivered = rst->delivered - rs->prior_delivered; + rs->lost = rst->lost - rs->prior_lost; + + if (rs->interval < cstat->min_rtt) { + rs->interval = UINT64_MAX; + return 0; + } + + if (!rs->interval) { + return 0; + } + + rate = rs->delivered * NGTCP2_SECONDS / rs->interval; + + if (rate > ngtcp2_window_filter_get_best(&rst->wf) || !rst->app_limited) { + ngtcp2_window_filter_update(&rst->wf, rate, rst->round_count); + cstat->delivery_rate_sec = ngtcp2_window_filter_get_best(&rst->wf); + } + + return 0; +} + +void ngtcp2_rst_update_rate_sample(ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent, + ngtcp2_tstamp ts) { + ngtcp2_rs *rs = &rst->rs; + + rst->delivered += ent->pktlen; + rst->delivered_ts = ts; + + if (ent->rst.delivered > rs->prior_delivered) { + rs->prior_delivered = ent->rst.delivered; + rs->prior_ts = ent->rst.delivered_ts; + rs->is_app_limited = ent->rst.is_app_limited; + rs->send_elapsed = ent->ts - ent->rst.first_sent_ts; + rs->ack_elapsed = rst->delivered_ts - ent->rst.delivered_ts; + rs->tx_in_flight = ent->rst.tx_in_flight; + rs->prior_lost = ent->rst.lost; + rst->first_sent_ts = ent->ts; + } +} + +void ngtcp2_rst_update_app_limited(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat) { + (void)rst; + (void)cstat; + /* TODO Not implemented */ +} diff --git a/lib/ngtcp2_rst.h b/lib/ngtcp2_rst.h new file mode 100644 index 0000000..488c655 --- /dev/null +++ b/lib/ngtcp2_rst.h @@ -0,0 +1,85 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_RST_H +#define NGTCP2_RST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_window_filter.h" + +typedef struct ngtcp2_rtb_entry ngtcp2_rtb_entry; + +/** + * @struct + * + * ngtcp2_rs contains connection state for delivery rate estimation. + */ +typedef struct ngtcp2_rs { + ngtcp2_duration interval; + uint64_t delivered; + uint64_t prior_delivered; + ngtcp2_tstamp prior_ts; + uint64_t tx_in_flight; + uint64_t lost; + uint64_t prior_lost; + ngtcp2_duration send_elapsed; + ngtcp2_duration ack_elapsed; + int is_app_limited; +} ngtcp2_rs; + +void ngtcp2_rs_init(ngtcp2_rs *rs); + +/* + * ngtcp2_rst implements delivery rate estimation described in + * https://tools.ietf.org/html/draft-cheng-iccrg-delivery-rate-estimation-00 + */ +typedef struct ngtcp2_rst { + ngtcp2_rs rs; + ngtcp2_window_filter wf; + uint64_t delivered; + ngtcp2_tstamp delivered_ts; + ngtcp2_tstamp first_sent_ts; + uint64_t app_limited; + uint64_t next_round_delivered; + uint64_t round_count; + uint64_t lost; + int is_cwnd_limited; +} ngtcp2_rst; + +void ngtcp2_rst_init(ngtcp2_rst *rst); + +void ngtcp2_rst_on_pkt_sent(ngtcp2_rst *rst, ngtcp2_rtb_entry *ent, + const ngtcp2_conn_stat *cstat); +int ngtcp2_rst_on_ack_recv(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat, + uint64_t pkt_delivered); +void ngtcp2_rst_update_rate_sample(ngtcp2_rst *rst, const ngtcp2_rtb_entry *ent, + ngtcp2_tstamp ts); +void ngtcp2_rst_update_app_limited(ngtcp2_rst *rst, ngtcp2_conn_stat *cstat); + +#endif /* NGTCP2_RST_H */ diff --git a/lib/ngtcp2_rtb.c b/lib/ngtcp2_rtb.c new file mode 100644 index 0000000..18db04b --- /dev/null +++ b/lib/ngtcp2_rtb.c @@ -0,0 +1,1673 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_rtb.h" + +#include <assert.h> +#include <string.h> + +#include "ngtcp2_macro.h" +#include "ngtcp2_conn.h" +#include "ngtcp2_log.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_cc.h" +#include "ngtcp2_rcvry.h" +#include "ngtcp2_rst.h" +#include "ngtcp2_unreachable.h" + +int ngtcp2_frame_chain_new(ngtcp2_frame_chain **pfrc, const ngtcp2_mem *mem) { + *pfrc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_frame_chain)); + if (*pfrc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_frame_chain_init(*pfrc); + + return 0; +} + +int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc, + ngtcp2_objalloc *objalloc) { + *pfrc = ngtcp2_objalloc_frame_chain_get(objalloc); + if (*pfrc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_frame_chain_init(*pfrc); + + return 0; +} + +int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen, + const ngtcp2_mem *mem) { + *pfrc = ngtcp2_mem_malloc(mem, sizeof(ngtcp2_frame_chain) + extralen); + if (*pfrc == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_frame_chain_init(*pfrc); + + return 0; +} + +int ngtcp2_frame_chain_stream_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc, + size_t datacnt, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { + size_t need, avail = sizeof(ngtcp2_frame) - sizeof(ngtcp2_stream); + + if (datacnt > 1) { + need = sizeof(ngtcp2_vec) * (datacnt - 1); + + if (need > avail) { + return ngtcp2_frame_chain_extralen_new(pfrc, need - avail, mem); + } + } + + return ngtcp2_frame_chain_objalloc_new(pfrc, objalloc); +} + +int ngtcp2_frame_chain_crypto_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc, + size_t datacnt, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { + size_t need, avail = sizeof(ngtcp2_frame) - sizeof(ngtcp2_crypto); + + if (datacnt > 1) { + need = sizeof(ngtcp2_vec) * (datacnt - 1); + + if (need > avail) { + return ngtcp2_frame_chain_extralen_new(pfrc, need - avail, mem); + } + } + + return ngtcp2_frame_chain_objalloc_new(pfrc, objalloc); +} + +int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, + const ngtcp2_vec *token, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { + size_t avail = sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token); + int rv; + uint8_t *p; + ngtcp2_frame *fr; + + if (token->len > avail) { + rv = ngtcp2_frame_chain_extralen_new(pfrc, token->len - avail, mem); + } else { + rv = ngtcp2_frame_chain_objalloc_new(pfrc, objalloc); + } + if (rv != 0) { + return rv; + } + + fr = &(*pfrc)->fr; + fr->type = NGTCP2_FRAME_NEW_TOKEN; + + p = (uint8_t *)fr + sizeof(ngtcp2_new_token); + memcpy(p, token->base, token->len); + + ngtcp2_vec_init(&fr->new_token.token, p, token->len); + + return 0; +} + +void ngtcp2_frame_chain_del(ngtcp2_frame_chain *frc, const ngtcp2_mem *mem) { + ngtcp2_frame_chain_binder *binder; + + if (frc == NULL) { + return; + } + + binder = frc->binder; + if (binder && --binder->refcount == 0) { + ngtcp2_mem_free(mem, binder); + } + + ngtcp2_mem_free(mem, frc); +} + +void ngtcp2_frame_chain_objalloc_del(ngtcp2_frame_chain *frc, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { + ngtcp2_frame_chain_binder *binder; + + if (frc == NULL) { + return; + } + + switch (frc->fr.type) { + case NGTCP2_FRAME_STREAM: + if (frc->fr.stream.datacnt && + sizeof(ngtcp2_vec) * (frc->fr.stream.datacnt - 1) > + sizeof(ngtcp2_frame) - sizeof(ngtcp2_stream)) { + ngtcp2_frame_chain_del(frc, mem); + + return; + } + + break; + case NGTCP2_FRAME_CRYPTO: + if (frc->fr.crypto.datacnt && + sizeof(ngtcp2_vec) * (frc->fr.crypto.datacnt - 1) > + sizeof(ngtcp2_frame) - sizeof(ngtcp2_crypto)) { + ngtcp2_frame_chain_del(frc, mem); + + return; + } + + break; + case NGTCP2_FRAME_NEW_TOKEN: + if (frc->fr.new_token.token.len > + sizeof(ngtcp2_frame) - sizeof(ngtcp2_new_token)) { + ngtcp2_frame_chain_del(frc, mem); + + return; + } + + break; + } + + binder = frc->binder; + if (binder && --binder->refcount == 0) { + ngtcp2_mem_free(mem, binder); + } + + frc->binder = NULL; + + ngtcp2_objalloc_frame_chain_release(objalloc, frc); +} + +void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc) { + frc->next = NULL; + frc->binder = NULL; +} + +void ngtcp2_frame_chain_list_objalloc_del(ngtcp2_frame_chain *frc, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem) { + ngtcp2_frame_chain *next; + + for (; frc; frc = next) { + next = frc->next; + + ngtcp2_frame_chain_objalloc_del(frc, objalloc, mem); + } +} + +int ngtcp2_frame_chain_binder_new(ngtcp2_frame_chain_binder **pbinder, + const ngtcp2_mem *mem) { + *pbinder = ngtcp2_mem_calloc(mem, 1, sizeof(ngtcp2_frame_chain_binder)); + if (*pbinder == NULL) { + return NGTCP2_ERR_NOMEM; + } + + return 0; +} + +int ngtcp2_bind_frame_chains(ngtcp2_frame_chain *a, ngtcp2_frame_chain *b, + const ngtcp2_mem *mem) { + ngtcp2_frame_chain_binder *binder; + int rv; + + assert(b->binder == NULL); + + if (a->binder == NULL) { + rv = ngtcp2_frame_chain_binder_new(&binder, mem); + if (rv != 0) { + return rv; + } + + a->binder = binder; + ++a->binder->refcount; + } + + b->binder = a->binder; + ++b->binder->refcount; + + return 0; +} + +static void rtb_entry_init(ngtcp2_rtb_entry *ent, const ngtcp2_pkt_hd *hd, + ngtcp2_frame_chain *frc, ngtcp2_tstamp ts, + size_t pktlen, uint16_t flags) { + memset(ent, 0, sizeof(*ent)); + + ent->hd.pkt_num = hd->pkt_num; + ent->hd.type = hd->type; + ent->hd.flags = hd->flags; + ent->frc = frc; + ent->ts = ts; + ent->lost_ts = UINT64_MAX; + ent->pktlen = pktlen; + ent->flags = flags; + ent->next = NULL; +} + +int ngtcp2_rtb_entry_objalloc_new(ngtcp2_rtb_entry **pent, + const ngtcp2_pkt_hd *hd, + ngtcp2_frame_chain *frc, ngtcp2_tstamp ts, + size_t pktlen, uint16_t flags, + ngtcp2_objalloc *objalloc) { + *pent = ngtcp2_objalloc_rtb_entry_get(objalloc); + if (*pent == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rtb_entry_init(*pent, hd, frc, ts, pktlen, flags); + + return 0; +} + +void ngtcp2_rtb_entry_objalloc_del(ngtcp2_rtb_entry *ent, + ngtcp2_objalloc *objalloc, + ngtcp2_objalloc *frc_objalloc, + const ngtcp2_mem *mem) { + ngtcp2_frame_chain_list_objalloc_del(ent->frc, frc_objalloc, mem); + + ent->frc = NULL; + + ngtcp2_objalloc_rtb_entry_release(objalloc, ent); +} + +static int greater(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) { + return *(int64_t *)lhs > *(int64_t *)rhs; +} + +void ngtcp2_rtb_init(ngtcp2_rtb *rtb, ngtcp2_pktns_id pktns_id, + ngtcp2_strm *crypto, ngtcp2_rst *rst, ngtcp2_cc *cc, + ngtcp2_log *log, ngtcp2_qlog *qlog, + ngtcp2_objalloc *rtb_entry_objalloc, + ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem) { + rtb->rtb_entry_objalloc = rtb_entry_objalloc; + rtb->frc_objalloc = frc_objalloc; + ngtcp2_ksl_init(&rtb->ents, greater, sizeof(int64_t), mem); + rtb->crypto = crypto; + rtb->rst = rst; + rtb->cc = cc; + rtb->log = log; + rtb->qlog = qlog; + rtb->mem = mem; + rtb->largest_acked_tx_pkt_num = -1; + rtb->num_ack_eliciting = 0; + rtb->num_retransmittable = 0; + rtb->num_pto_eliciting = 0; + rtb->probe_pkt_left = 0; + rtb->pktns_id = pktns_id; + rtb->cc_pkt_num = 0; + rtb->cc_bytes_in_flight = 0; + rtb->persistent_congestion_start_ts = UINT64_MAX; + rtb->num_lost_pkts = 0; + rtb->num_lost_pmtud_pkts = 0; +} + +void ngtcp2_rtb_free(ngtcp2_rtb *rtb) { + ngtcp2_ksl_it it; + + if (rtb == NULL) { + return; + } + + it = ngtcp2_ksl_begin(&rtb->ents); + + for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { + ngtcp2_rtb_entry_objalloc_del(ngtcp2_ksl_it_get(&it), + rtb->rtb_entry_objalloc, rtb->frc_objalloc, + rtb->mem); + } + + ngtcp2_ksl_free(&rtb->ents); +} + +static void rtb_on_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat) { + ngtcp2_rst_on_pkt_sent(rtb->rst, ent, cstat); + + assert(rtb->cc_pkt_num <= ent->hd.pkt_num); + + cstat->bytes_in_flight += ent->pktlen; + rtb->cc_bytes_in_flight += ent->pktlen; + + ngtcp2_rst_update_app_limited(rtb->rst, cstat); + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + ++rtb->num_ack_eliciting; + } + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) { + ++rtb->num_retransmittable; + } + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING) { + ++rtb->num_pto_eliciting; + } +} + +static size_t rtb_on_remove(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat) { + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) { + assert(rtb->num_lost_pkts); + --rtb->num_lost_pkts; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + assert(rtb->num_lost_pmtud_pkts); + --rtb->num_lost_pmtud_pkts; + } + + return 0; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + assert(rtb->num_ack_eliciting); + --rtb->num_ack_eliciting; + } + + if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) && + !(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED)) { + assert(rtb->num_retransmittable); + --rtb->num_retransmittable; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING) { + assert(rtb->num_pto_eliciting); + --rtb->num_pto_eliciting; + } + + if (rtb->cc_pkt_num <= ent->hd.pkt_num) { + assert(cstat->bytes_in_flight >= ent->pktlen); + cstat->bytes_in_flight -= ent->pktlen; + + assert(rtb->cc_bytes_in_flight >= ent->pktlen); + rtb->cc_bytes_in_flight -= ent->pktlen; + + /* If PMTUD packet is lost, we do not report the lost bytes to the + caller in order to ignore loss of PMTUD packet. */ + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + return 0; + } + + return ent->pktlen; + } + + return 0; +} + +/* NGTCP2_RECLAIM_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_RECLAIM_FLAG_NONE 0x00u +/* NGTCP2_RECLAIM_FLAG_ON_LOSS indicates that frames are reclaimed + because of the packet loss.*/ +#define NGTCP2_RECLAIM_FLAG_ON_LOSS 0x01u + +/* + * rtb_reclaim_frame queues unacknowledged frames included in |ent| + * for retransmission. The re-queued frames are not deleted from + * |ent|. It returns the number of frames queued. |flags| is bitwise + * OR of 0 or more of NGTCP2_RECLAIM_FLAG_*. + */ +static ngtcp2_ssize rtb_reclaim_frame(ngtcp2_rtb *rtb, uint8_t flags, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_rtb_entry *ent) { + ngtcp2_frame_chain *frc, *nfrc, **pfrc = &pktns->tx.frq; + ngtcp2_frame *fr; + ngtcp2_strm *strm; + ngtcp2_range gap, range; + size_t num_reclaimed = 0; + int rv; + int streamfrq_empty; + + assert(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE); + + /* TODO Reconsider the order of pfrc */ + for (frc = ent->frc; frc; frc = frc->next) { + fr = &frc->fr; + /* Check that a late ACK acknowledged this frame. */ + if (frc->binder && + (frc->binder->flags & NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK)) { + continue; + } + switch (frc->fr.type) { + case NGTCP2_FRAME_STREAM: + strm = ngtcp2_conn_find_stream(conn, fr->stream.stream_id); + if (strm == NULL) { + continue; + } + + gap = ngtcp2_strm_get_unacked_range_after(strm, fr->stream.offset); + + range.begin = fr->stream.offset; + range.end = fr->stream.offset + + ngtcp2_vec_len(fr->stream.data, fr->stream.datacnt); + range = ngtcp2_range_intersect(&range, &gap); + if (ngtcp2_range_len(&range) == 0) { + if (!fr->stream.fin) { + /* 0 length STREAM frame with offset == 0 must be + retransmitted if no non-empty data is sent to this stream + and no data in this stream is acknowledged. */ + if (fr->stream.offset != 0 || fr->stream.datacnt != 0 || + strm->tx.offset || (strm->flags & NGTCP2_STRM_FLAG_ANY_ACKED)) { + continue; + } + } else if (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED) { + continue; + } + } + + if ((flags & NGTCP2_RECLAIM_FLAG_ON_LOSS) && + ent->hd.pkt_num != strm->tx.last_lost_pkt_num) { + strm->tx.last_lost_pkt_num = ent->hd.pkt_num; + ++strm->tx.loss_count; + } + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, fr->stream.datacnt, rtb->frc_objalloc, rtb->mem); + if (rv != 0) { + return rv; + } + + nfrc->fr = *fr; + ngtcp2_vec_copy(nfrc->fr.stream.data, fr->stream.data, + fr->stream.datacnt); + + streamfrq_empty = ngtcp2_strm_streamfrq_empty(strm); + rv = ngtcp2_strm_streamfrq_push(strm, nfrc); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(nfrc, rtb->frc_objalloc, rtb->mem); + return rv; + } + if (!ngtcp2_strm_is_tx_queued(strm)) { + strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn); + rv = ngtcp2_conn_tx_strmq_push(conn, strm); + if (rv != 0) { + return rv; + } + } + if (streamfrq_empty) { + ++conn->tx.strmq_nretrans; + } + + ++num_reclaimed; + + continue; + case NGTCP2_FRAME_CRYPTO: + /* Don't resend CRYPTO frame if the whole region it contains has + been acknowledged */ + gap = ngtcp2_strm_get_unacked_range_after(rtb->crypto, fr->crypto.offset); + + range.begin = fr->crypto.offset; + range.end = fr->crypto.offset + + ngtcp2_vec_len(fr->crypto.data, fr->crypto.datacnt); + range = ngtcp2_range_intersect(&range, &gap); + if (ngtcp2_range_len(&range) == 0) { + continue; + } + + rv = ngtcp2_frame_chain_crypto_datacnt_objalloc_new( + &nfrc, fr->crypto.datacnt, rtb->frc_objalloc, rtb->mem); + if (rv != 0) { + return rv; + } + + nfrc->fr = *fr; + ngtcp2_vec_copy(nfrc->fr.crypto.data, fr->crypto.data, + fr->crypto.datacnt); + + rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, + &nfrc->fr.crypto.offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, rtb->frc_objalloc, rtb->mem); + return rv; + } + + ++num_reclaimed; + + continue; + case NGTCP2_FRAME_NEW_TOKEN: + rv = ngtcp2_frame_chain_new_token_objalloc_new( + &nfrc, &fr->new_token.token, rtb->frc_objalloc, rtb->mem); + if (rv != 0) { + return rv; + } + + rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem); + if (rv != 0) { + return rv; + } + + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + continue; + default: + rv = ngtcp2_frame_chain_objalloc_new(&nfrc, rtb->frc_objalloc); + if (rv != 0) { + return rv; + } + + nfrc->fr = *fr; + + rv = ngtcp2_bind_frame_chains(frc, nfrc, rtb->mem); + if (rv != 0) { + return rv; + } + + break; + } + + ++num_reclaimed; + + nfrc->next = *pfrc; + *pfrc = nfrc; + pfrc = &nfrc->next; + } + + return (ngtcp2_ssize)num_reclaimed; +} + +/* + * conn_process_lost_datagram calls ngtcp2_lost_datagram callback for + * lost DATAGRAM frames. + */ +static int conn_process_lost_datagram(ngtcp2_conn *conn, + ngtcp2_rtb_entry *ent) { + ngtcp2_frame_chain *frc; + int rv; + + for (frc = ent->frc; frc; frc = frc->next) { + switch (frc->fr.type) { + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + assert(conn->callbacks.lost_datagram); + + rv = conn->callbacks.lost_datagram(conn, frc->fr.datagram.dgram_id, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + break; + } + } + + return 0; +} + +static int rtb_on_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_ksl_it *it, + ngtcp2_rtb_entry *ent, ngtcp2_conn_stat *cstat, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_tstamp ts) { + int rv; + ngtcp2_ssize reclaimed; + ngtcp2_cc *cc = rtb->cc; + ngtcp2_cc_pkt pkt; + + ngtcp2_log_pkt_lost(rtb->log, ent->hd.pkt_num, ent->hd.type, ent->hd.flags, + ent->ts); + + if (rtb->qlog) { + ngtcp2_qlog_pkt_lost(rtb->qlog, ent); + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + ++rtb->num_lost_pmtud_pkts; + } else if (rtb->cc->on_pkt_lost) { + cc->on_pkt_lost(cc, cstat, + ngtcp2_cc_pkt_init(&pkt, ent->hd.pkt_num, ent->pktlen, + rtb->pktns_id, ent->ts, ent->rst.lost, + ent->rst.tx_in_flight, + ent->rst.is_app_limited), + ts); + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED) { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 " has already been reclaimed on PTO", + ent->hd.pkt_num); + assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)); + assert(UINT64_MAX == ent->lost_ts); + + ent->flags |= NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED; + ent->lost_ts = ts; + + ++rtb->num_lost_pkts; + + ngtcp2_ksl_it_next(it); + + return 0; + } + + if (conn->callbacks.lost_datagram && + (ent->flags & NGTCP2_RTB_ENTRY_FLAG_DATAGRAM)) { + rv = conn_process_lost_datagram(conn, ent); + if (rv != 0) { + return rv; + } + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) { + assert(ent->frc); + assert(!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)); + assert(UINT64_MAX == ent->lost_ts); + + reclaimed = + rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_ON_LOSS, conn, pktns, ent); + if (reclaimed < 0) { + return (int)reclaimed; + } + } + + ent->flags |= NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED; + ent->lost_ts = ts; + + ++rtb->num_lost_pkts; + + ngtcp2_ksl_it_next(it); + + return 0; +} + +int ngtcp2_rtb_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat) { + int rv; + + rv = ngtcp2_ksl_insert(&rtb->ents, NULL, &ent->hd.pkt_num, ent); + if (rv != 0) { + return rv; + } + + rtb_on_add(rtb, ent, cstat); + + return 0; +} + +ngtcp2_ksl_it ngtcp2_rtb_head(ngtcp2_rtb *rtb) { + return ngtcp2_ksl_begin(&rtb->ents); +} + +static void rtb_remove(ngtcp2_rtb *rtb, ngtcp2_ksl_it *it, + ngtcp2_rtb_entry **pent, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat) { + int rv; + (void)rv; + + rv = ngtcp2_ksl_remove_hint(&rtb->ents, it, it, &ent->hd.pkt_num); + assert(0 == rv); + rtb_on_remove(rtb, ent, cstat); + + assert(ent->next == NULL); + + ngtcp2_list_insert(ent, pent); +} + +static void conn_ack_crypto_data(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + uint64_t datalen) { + ngtcp2_buf_chain **pbufchain, *bufchain; + size_t left; + + for (pbufchain = &pktns->crypto.tx.data; *pbufchain;) { + left = ngtcp2_buf_len(&(*pbufchain)->buf); + if (left > datalen) { + (*pbufchain)->buf.pos += datalen; + return; + } + + bufchain = *pbufchain; + *pbufchain = bufchain->next; + + ngtcp2_mem_free(conn->mem, bufchain); + + datalen -= left; + + if (datalen == 0) { + return; + } + } + + assert(datalen == 0); + + return; +} + +static int rtb_process_acked_pkt(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn *conn) { + ngtcp2_frame_chain *frc; + uint64_t prev_stream_offset, stream_offset; + ngtcp2_strm *strm; + int rv; + uint64_t datalen; + ngtcp2_strm *crypto = rtb->crypto; + ngtcp2_pktns *pktns = NULL; + + if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) && conn->pmtud && + conn->pmtud->tx_pkt_num <= ent->hd.pkt_num) { + ngtcp2_pmtud_probe_success(conn->pmtud, ent->pktlen); + + conn->dcid.current.max_udp_payload_size = + ngtcp2_max(conn->dcid.current.max_udp_payload_size, ent->pktlen); + + if (ngtcp2_pmtud_finished(conn->pmtud)) { + ngtcp2_conn_stop_pmtud(conn); + } + } + + for (frc = ent->frc; frc; frc = frc->next) { + if (frc->binder) { + frc->binder->flags |= NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK; + } + + switch (frc->fr.type) { + case NGTCP2_FRAME_STREAM: + strm = ngtcp2_conn_find_stream(conn, frc->fr.stream.stream_id); + if (strm == NULL) { + break; + } + + strm->flags |= NGTCP2_STRM_FLAG_ANY_ACKED; + + if (frc->fr.stream.fin) { + strm->flags |= NGTCP2_STRM_FLAG_FIN_ACKED; + } + + prev_stream_offset = ngtcp2_strm_get_acked_offset(strm); + rv = ngtcp2_strm_ack_data( + strm, frc->fr.stream.offset, + ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt)); + if (rv != 0) { + return rv; + } + + if (conn->callbacks.acked_stream_data_offset) { + stream_offset = ngtcp2_strm_get_acked_offset(strm); + datalen = stream_offset - prev_stream_offset; + if (datalen == 0 && !frc->fr.stream.fin) { + break; + } + + rv = conn->callbacks.acked_stream_data_offset( + conn, strm->stream_id, prev_stream_offset, datalen, conn->user_data, + strm->stream_user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + } + + rv = ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_CRYPTO: + prev_stream_offset = ngtcp2_strm_get_acked_offset(crypto); + rv = ngtcp2_strm_ack_data( + crypto, frc->fr.crypto.offset, + ngtcp2_vec_len(frc->fr.crypto.data, frc->fr.crypto.datacnt)); + if (rv != 0) { + return rv; + } + + stream_offset = ngtcp2_strm_get_acked_offset(crypto); + datalen = stream_offset - prev_stream_offset; + if (datalen == 0) { + break; + } + + switch (rtb->pktns_id) { + case NGTCP2_PKTNS_ID_INITIAL: + pktns = conn->in_pktns; + break; + case NGTCP2_PKTNS_ID_HANDSHAKE: + pktns = conn->hs_pktns; + break; + case NGTCP2_PKTNS_ID_APPLICATION: + pktns = &conn->pktns; + break; + default: + ngtcp2_unreachable(); + } + + conn_ack_crypto_data(conn, pktns, datalen); + + break; + case NGTCP2_FRAME_RESET_STREAM: + strm = ngtcp2_conn_find_stream(conn, frc->fr.reset_stream.stream_id); + if (strm == NULL) { + break; + } + strm->flags |= NGTCP2_STRM_FLAG_RST_ACKED; + rv = ngtcp2_conn_close_stream_if_shut_rdwr(conn, strm); + if (rv != 0) { + return rv; + } + break; + case NGTCP2_FRAME_RETIRE_CONNECTION_ID: + ngtcp2_conn_untrack_retired_dcid_seq(conn, + frc->fr.retire_connection_id.seq); + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + if (!conn->callbacks.ack_datagram) { + break; + } + + rv = conn->callbacks.ack_datagram(conn, frc->fr.datagram.dgram_id, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + break; + } + } + return 0; +} + +static void rtb_on_pkt_acked(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { + ngtcp2_cc *cc = rtb->cc; + ngtcp2_cc_pkt pkt; + + ngtcp2_rst_update_rate_sample(rtb->rst, ent, ts); + + cc->on_pkt_acked(cc, cstat, + ngtcp2_cc_pkt_init(&pkt, ent->hd.pkt_num, ent->pktlen, + rtb->pktns_id, ent->ts, ent->rst.lost, + ent->rst.tx_in_flight, + ent->rst.is_app_limited), + ts); + + if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PROBE) && + (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING)) { + cstat->pto_count = 0; + } +} + +static void conn_verify_ecn(ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_cc *cc, ngtcp2_conn_stat *cstat, + const ngtcp2_ack *fr, size_t ecn_acked, + ngtcp2_tstamp largest_acked_sent_ts, + ngtcp2_tstamp ts) { + if (conn->tx.ecn.state == NGTCP2_ECN_STATE_FAILED) { + return; + } + + if ((ecn_acked && fr->type == NGTCP2_FRAME_ACK) || + (fr->type == NGTCP2_FRAME_ACK_ECN && + (pktns->rx.ecn.ack.ect0 > fr->ecn.ect0 || + pktns->rx.ecn.ack.ect1 > fr->ecn.ect1 || + pktns->rx.ecn.ack.ce > fr->ecn.ce || + (fr->ecn.ect0 - pktns->rx.ecn.ack.ect0) + + (fr->ecn.ce - pktns->rx.ecn.ack.ce) < + ecn_acked || + fr->ecn.ect0 > pktns->tx.ecn.ect0 || fr->ecn.ect1))) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, + "path is not ECN capable"); + conn->tx.ecn.state = NGTCP2_ECN_STATE_FAILED; + return; + } + + if (conn->tx.ecn.state != NGTCP2_ECN_STATE_CAPABLE && ecn_acked) { + ngtcp2_log_info(&conn->log, NGTCP2_LOG_EVENT_CON, "path is ECN capable"); + conn->tx.ecn.state = NGTCP2_ECN_STATE_CAPABLE; + } + + if (fr->type == NGTCP2_FRAME_ACK_ECN) { + if (largest_acked_sent_ts != UINT64_MAX && + fr->ecn.ce > pktns->rx.ecn.ack.ce) { + cc->congestion_event(cc, cstat, largest_acked_sent_ts, ts); + } + + pktns->rx.ecn.ack.ect0 = fr->ecn.ect0; + pktns->rx.ecn.ack.ect1 = fr->ecn.ect1; + pktns->rx.ecn.ack.ce = fr->ecn.ce; + } +} + +static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts); + +ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + ngtcp2_conn_stat *cstat, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_tstamp pkt_ts, + ngtcp2_tstamp ts) { + ngtcp2_rtb_entry *ent; + int64_t largest_ack = fr->largest_ack, min_ack; + size_t i; + int rv; + ngtcp2_ksl_it it; + ngtcp2_ssize num_acked = 0; + ngtcp2_tstamp largest_pkt_sent_ts = UINT64_MAX; + ngtcp2_tstamp largest_acked_sent_ts = UINT64_MAX; + int64_t pkt_num; + ngtcp2_cc *cc = rtb->cc; + ngtcp2_rtb_entry *acked_ent = NULL; + int ack_eliciting_pkt_acked = 0; + size_t ecn_acked = 0; + int verify_ecn = 0; + ngtcp2_cc_ack cc_ack = {0}; + size_t num_lost_pkts = rtb->num_lost_pkts - rtb->num_lost_pmtud_pkts; + + cc_ack.prior_bytes_in_flight = cstat->bytes_in_flight; + cc_ack.rtt = UINT64_MAX; + + if (conn && (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED) && + (conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR) && + largest_ack >= conn->pktns.crypto.tx.ckm->pkt_num) { + conn->flags &= (uint32_t) ~(NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED | + NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR); + conn->crypto.key_update.confirmed_ts = ts; + + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_CRY, "key update confirmed"); + } + + if (rtb->largest_acked_tx_pkt_num < largest_ack) { + rtb->largest_acked_tx_pkt_num = largest_ack; + verify_ecn = 1; + } + + /* Assume that ngtcp2_pkt_validate_ack(fr) returns 0 */ + it = ngtcp2_ksl_lower_bound(&rtb->ents, &largest_ack); + if (ngtcp2_ksl_it_end(&it)) { + if (conn && verify_ecn) { + conn_verify_ecn(conn, pktns, rtb->cc, cstat, fr, ecn_acked, + largest_acked_sent_ts, ts); + } + return 0; + } + + min_ack = largest_ack - (int64_t)fr->first_ack_range; + + for (; !ngtcp2_ksl_it_end(&it);) { + pkt_num = *(int64_t *)ngtcp2_ksl_it_key(&it); + + assert(pkt_num <= largest_ack); + + if (pkt_num < min_ack) { + break; + } + + ent = ngtcp2_ksl_it_get(&it); + + if (largest_ack == pkt_num) { + largest_pkt_sent_ts = ent->ts; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + ack_eliciting_pkt_acked = 1; + } + + rtb_remove(rtb, &it, &acked_ent, ent, cstat); + ++num_acked; + } + + for (i = 0; i < fr->rangecnt;) { + largest_ack = min_ack - (int64_t)fr->ranges[i].gap - 2; + min_ack = largest_ack - (int64_t)fr->ranges[i].len; + + it = ngtcp2_ksl_lower_bound(&rtb->ents, &largest_ack); + if (ngtcp2_ksl_it_end(&it)) { + break; + } + + for (; !ngtcp2_ksl_it_end(&it);) { + pkt_num = *(int64_t *)ngtcp2_ksl_it_key(&it); + if (pkt_num < min_ack) { + break; + } + ent = ngtcp2_ksl_it_get(&it); + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING) { + ack_eliciting_pkt_acked = 1; + } + + rtb_remove(rtb, &it, &acked_ent, ent, cstat); + ++num_acked; + } + + ++i; + } + + if (largest_pkt_sent_ts != UINT64_MAX && ack_eliciting_pkt_acked) { + cc_ack.rtt = pkt_ts - largest_pkt_sent_ts; + + rv = ngtcp2_conn_update_rtt(conn, cc_ack.rtt, fr->ack_delay_unscaled, ts); + if (rv == 0 && cc->new_rtt_sample) { + cc->new_rtt_sample(cc, cstat, ts); + } + } + + if (conn) { + for (ent = acked_ent; ent; ent = acked_ent) { + if (ent->hd.pkt_num >= pktns->tx.ecn.start_pkt_num && + (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ECN)) { + ++ecn_acked; + } + + assert(largest_acked_sent_ts == UINT64_MAX || + largest_acked_sent_ts <= ent->ts); + + largest_acked_sent_ts = ent->ts; + + rv = rtb_process_acked_pkt(rtb, ent, conn); + if (rv != 0) { + goto fail; + } + + if (ent->hd.pkt_num >= rtb->cc_pkt_num) { + assert(cc_ack.pkt_delivered <= ent->rst.delivered); + + cc_ack.bytes_delivered += ent->pktlen; + cc_ack.pkt_delivered = ent->rst.delivered; + } + + rtb_on_pkt_acked(rtb, ent, cstat, ts); + acked_ent = ent->next; + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } + + if (verify_ecn) { + conn_verify_ecn(conn, pktns, rtb->cc, cstat, fr, ecn_acked, + largest_acked_sent_ts, ts); + } + } else { + /* For unit tests */ + for (ent = acked_ent; ent; ent = acked_ent) { + rtb_on_pkt_acked(rtb, ent, cstat, ts); + acked_ent = ent->next; + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } + } + + if (rtb->cc->on_spurious_congestion && num_lost_pkts && + rtb->num_lost_pkts - rtb->num_lost_pmtud_pkts == 0) { + rtb->cc->on_spurious_congestion(cc, cstat, ts); + } + + ngtcp2_rst_on_ack_recv(rtb->rst, cstat, cc_ack.pkt_delivered); + + if (conn && num_acked > 0) { + rv = rtb_detect_lost_pkt(rtb, &cc_ack.bytes_lost, conn, pktns, cstat, ts); + if (rv != 0) { + return rv; + } + } + + rtb->rst->lost += cc_ack.bytes_lost; + + cc_ack.largest_acked_sent_ts = largest_acked_sent_ts; + cc->on_ack_recv(cc, cstat, &cc_ack, ts); + + return num_acked; + +fail: + for (ent = acked_ent; ent; ent = acked_ent) { + acked_ent = ent->next; + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } + + return rv; +} + +static int rtb_pkt_lost(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat, + const ngtcp2_rtb_entry *ent, ngtcp2_duration loss_delay, + size_t pkt_thres, ngtcp2_tstamp ts) { + ngtcp2_tstamp loss_time; + + if (ent->ts + loss_delay <= ts || + rtb->largest_acked_tx_pkt_num >= ent->hd.pkt_num + (int64_t)pkt_thres) { + return 1; + } + + loss_time = cstat->loss_time[rtb->pktns_id]; + + if (loss_time == UINT64_MAX) { + loss_time = ent->ts + loss_delay; + } else { + loss_time = ngtcp2_min(loss_time, ent->ts + loss_delay); + } + + cstat->loss_time[rtb->pktns_id] = loss_time; + + return 0; +} + +/* + * rtb_compute_pkt_loss_delay computes loss delay. + */ +static ngtcp2_duration compute_pkt_loss_delay(const ngtcp2_conn_stat *cstat) { + /* 9/8 is kTimeThreshold */ + ngtcp2_duration loss_delay = + ngtcp2_max(cstat->latest_rtt, cstat->smoothed_rtt) * 9 / 8; + return ngtcp2_max(loss_delay, NGTCP2_GRANULARITY); +} + +/* + * conn_all_ecn_pkt_lost returns nonzero if all ECN QUIC packets are + * lost during validation period. + */ +static int conn_all_ecn_pkt_lost(ngtcp2_conn *conn) { + ngtcp2_pktns *in_pktns = conn->in_pktns; + ngtcp2_pktns *hs_pktns = conn->hs_pktns; + ngtcp2_pktns *pktns = &conn->pktns; + + return (!in_pktns || in_pktns->tx.ecn.validation_pkt_sent == + in_pktns->tx.ecn.validation_pkt_lost) && + (!hs_pktns || hs_pktns->tx.ecn.validation_pkt_sent == + hs_pktns->tx.ecn.validation_pkt_lost) && + pktns->tx.ecn.validation_pkt_sent == pktns->tx.ecn.validation_pkt_lost; +} + +static int rtb_detect_lost_pkt(ngtcp2_rtb *rtb, uint64_t *ppkt_lost, + ngtcp2_conn *conn, ngtcp2_pktns *pktns, + ngtcp2_conn_stat *cstat, ngtcp2_tstamp ts) { + ngtcp2_rtb_entry *ent; + ngtcp2_duration loss_delay; + ngtcp2_ksl_it it; + ngtcp2_tstamp latest_ts, oldest_ts; + int64_t last_lost_pkt_num; + ngtcp2_duration loss_window, congestion_period; + ngtcp2_cc *cc = rtb->cc; + int rv; + uint64_t pkt_thres = + rtb->cc_bytes_in_flight / cstat->max_tx_udp_payload_size / 2; + size_t ecn_pkt_lost = 0; + ngtcp2_tstamp start_ts; + ngtcp2_duration pto = ngtcp2_conn_compute_pto(conn, pktns); + uint64_t bytes_lost = 0; + ngtcp2_duration max_ack_delay; + + pkt_thres = ngtcp2_max(pkt_thres, NGTCP2_PKT_THRESHOLD); + pkt_thres = ngtcp2_min(pkt_thres, 256); + cstat->loss_time[rtb->pktns_id] = UINT64_MAX; + loss_delay = compute_pkt_loss_delay(cstat); + + it = ngtcp2_ksl_lower_bound(&rtb->ents, &rtb->largest_acked_tx_pkt_num); + for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { + ent = ngtcp2_ksl_it_get(&it); + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) { + break; + } + + if (rtb_pkt_lost(rtb, cstat, ent, loss_delay, (size_t)pkt_thres, ts)) { + /* All entries from ent are considered to be lost. */ + latest_ts = oldest_ts = ent->ts; + last_lost_pkt_num = ent->hd.pkt_num; + max_ack_delay = conn->remote.transport_params + ? conn->remote.transport_params->max_ack_delay + : 0; + + congestion_period = + (cstat->smoothed_rtt + + ngtcp2_max(4 * cstat->rttvar, NGTCP2_GRANULARITY) + max_ack_delay) * + NGTCP2_PERSISTENT_CONGESTION_THRESHOLD; + + start_ts = ngtcp2_max(rtb->persistent_congestion_start_ts, + cstat->first_rtt_sample_ts); + + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + + if (last_lost_pkt_num == ent->hd.pkt_num + 1 && ent->ts >= start_ts) { + last_lost_pkt_num = ent->hd.pkt_num; + oldest_ts = ent->ts; + } else { + last_lost_pkt_num = -1; + } + + if ((ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)) { + if (rtb->pktns_id != NGTCP2_PKTNS_ID_APPLICATION || + last_lost_pkt_num == -1 || + latest_ts - oldest_ts >= congestion_period) { + break; + } + ngtcp2_ksl_it_next(&it); + continue; + } + + if (ent->hd.pkt_num >= pktns->tx.ecn.start_pkt_num && + (ent->flags & NGTCP2_RTB_ENTRY_FLAG_ECN)) { + ++ecn_pkt_lost; + } + + bytes_lost += rtb_on_remove(rtb, ent, cstat); + rv = rtb_on_pkt_lost(rtb, &it, ent, cstat, conn, pktns, ts); + if (rv != 0) { + return rv; + } + } + + /* If only PMTUD packets are lost, do not trigger congestion + event. */ + if (bytes_lost == 0) { + break; + } + + switch (conn->tx.ecn.state) { + case NGTCP2_ECN_STATE_TESTING: + if (conn->tx.ecn.validation_start_ts == UINT64_MAX) { + break; + } + if (ts - conn->tx.ecn.validation_start_ts < 3 * pto) { + pktns->tx.ecn.validation_pkt_lost += ecn_pkt_lost; + assert(pktns->tx.ecn.validation_pkt_sent >= + pktns->tx.ecn.validation_pkt_lost); + break; + } + conn->tx.ecn.state = NGTCP2_ECN_STATE_UNKNOWN; + /* fall through */ + case NGTCP2_ECN_STATE_UNKNOWN: + pktns->tx.ecn.validation_pkt_lost += ecn_pkt_lost; + assert(pktns->tx.ecn.validation_pkt_sent >= + pktns->tx.ecn.validation_pkt_lost); + if (conn_all_ecn_pkt_lost(conn)) { + conn->tx.ecn.state = NGTCP2_ECN_STATE_FAILED; + } + break; + default: + break; + } + + cc->congestion_event(cc, cstat, latest_ts, ts); + + loss_window = latest_ts - oldest_ts; + /* Persistent congestion situation is only evaluated for app + * packet number space and for the packets sent after handshake + * is confirmed. During handshake, there is not much packets + * sent and also people seem to do lots of effort not to trigger + * persistent congestion there, then it is a lot easier to just + * not enable it during handshake. + */ + if (rtb->pktns_id == NGTCP2_PKTNS_ID_APPLICATION && loss_window > 0) { + if (loss_window >= congestion_period) { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "persistent congestion loss_window=%" PRIu64 + " congestion_period=%" PRIu64, + loss_window, congestion_period); + + /* Reset min_rtt, srtt, and rttvar here. Next new RTT + sample will be used to recalculate these values. */ + cstat->min_rtt = UINT64_MAX; + cstat->smoothed_rtt = conn->local.settings.initial_rtt; + cstat->rttvar = conn->local.settings.initial_rtt / 2; + cstat->first_rtt_sample_ts = UINT64_MAX; + + cc->on_persistent_congestion(cc, cstat, ts); + } + } + + break; + } + } + + ngtcp2_rtb_remove_excessive_lost_pkt(rtb, (size_t)pkt_thres); + + if (ppkt_lost) { + *ppkt_lost = bytes_lost; + } + + return 0; +} + +int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts) { + return rtb_detect_lost_pkt(rtb, /* ppkt_lost = */ NULL, conn, pktns, cstat, + ts); +} + +void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n) { + ngtcp2_ksl_it it = ngtcp2_ksl_end(&rtb->ents); + ngtcp2_rtb_entry *ent; + int rv; + (void)rv; + + for (; rtb->num_lost_pkts > n;) { + assert(ngtcp2_ksl_it_end(&it)); + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + assert(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED); + + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "removing stale lost pkn=%" PRId64, ent->hd.pkt_num); + + --rtb->num_lost_pkts; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + --rtb->num_lost_pmtud_pkts; + } + + rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num); + assert(0 == rv); + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } +} + +void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_duration pto, + ngtcp2_tstamp ts) { + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + int rv; + (void)rv; + + if (ngtcp2_ksl_len(&rtb->ents) == 0) { + return; + } + + it = ngtcp2_ksl_end(&rtb->ents); + + for (;;) { + assert(ngtcp2_ksl_it_end(&it)); + + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) || + ts - ent->lost_ts < pto) { + return; + } + + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "removing stale lost pkn=%" PRId64, ent->hd.pkt_num); + + --rtb->num_lost_pkts; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + --rtb->num_lost_pmtud_pkts; + } + + rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num); + assert(0 == rv); + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + + if (ngtcp2_ksl_len(&rtb->ents) == 0) { + return; + } + } +} + +ngtcp2_tstamp ngtcp2_rtb_lost_pkt_ts(ngtcp2_rtb *rtb) { + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + + if (ngtcp2_ksl_len(&rtb->ents) == 0) { + return UINT64_MAX; + } + + it = ngtcp2_ksl_end(&rtb->ents); + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED)) { + return UINT64_MAX; + } + + return ent->lost_ts; +} + +static int rtb_on_pkt_lost_resched_move(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, + ngtcp2_rtb_entry *ent) { + ngtcp2_frame_chain **pfrc, *frc; + ngtcp2_stream *sfr; + ngtcp2_strm *strm; + int rv; + int streamfrq_empty; + + ngtcp2_log_pkt_lost(rtb->log, ent->hd.pkt_num, ent->hd.type, ent->hd.flags, + ent->ts); + + if (rtb->qlog) { + ngtcp2_qlog_pkt_lost(rtb->qlog, ent); + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PROBE) { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 + " is a probe packet, no retransmission is necessary", + ent->hd.pkt_num); + return 0; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 + " is a PMTUD probe packet, no retransmission is necessary", + ent->hd.pkt_num); + return 0; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED) { + --rtb->num_lost_pkts; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE) { + --rtb->num_lost_pmtud_pkts; + } + + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 + " was declared lost and has already been retransmitted", + ent->hd.pkt_num); + return 0; + } + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED) { + ngtcp2_log_info(rtb->log, NGTCP2_LOG_EVENT_RCV, + "pkn=%" PRId64 " has already been reclaimed on PTO", + ent->hd.pkt_num); + return 0; + } + + if (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE) && + (!(ent->flags & NGTCP2_RTB_ENTRY_FLAG_DATAGRAM) || + !conn->callbacks.lost_datagram)) { + /* PADDING only (or PADDING + ACK ) packets will have NULL + ent->frc. */ + return 0; + } + + pfrc = &ent->frc; + + for (; *pfrc;) { + switch ((*pfrc)->fr.type) { + case NGTCP2_FRAME_STREAM: + frc = *pfrc; + + *pfrc = frc->next; + frc->next = NULL; + sfr = &frc->fr.stream; + + strm = ngtcp2_conn_find_stream(conn, sfr->stream_id); + if (!strm) { + ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem); + break; + } + streamfrq_empty = ngtcp2_strm_streamfrq_empty(strm); + rv = ngtcp2_strm_streamfrq_push(strm, frc); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem); + return rv; + } + if (!ngtcp2_strm_is_tx_queued(strm)) { + strm->cycle = ngtcp2_conn_tx_strmq_first_cycle(conn); + rv = ngtcp2_conn_tx_strmq_push(conn, strm); + if (rv != 0) { + return rv; + } + } + if (streamfrq_empty) { + ++conn->tx.strmq_nretrans; + } + break; + case NGTCP2_FRAME_CRYPTO: + frc = *pfrc; + + *pfrc = frc->next; + frc->next = NULL; + + rv = ngtcp2_ksl_insert(&pktns->crypto.tx.frq, NULL, + &frc->fr.crypto.offset, frc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem); + return rv; + } + break; + case NGTCP2_FRAME_DATAGRAM: + case NGTCP2_FRAME_DATAGRAM_LEN: + frc = *pfrc; + + if (conn->callbacks.lost_datagram) { + rv = conn->callbacks.lost_datagram(conn, frc->fr.datagram.dgram_id, + conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + } + + *pfrc = (*pfrc)->next; + + ngtcp2_frame_chain_objalloc_del(frc, rtb->frc_objalloc, rtb->mem); + break; + default: + pfrc = &(*pfrc)->next; + } + } + + *pfrc = pktns->tx.frq; + pktns->tx.frq = ent->frc; + ent->frc = NULL; + + return 0; +} + +int ngtcp2_rtb_remove_all(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat) { + ngtcp2_rtb_entry *ent; + ngtcp2_ksl_it it; + int rv; + + it = ngtcp2_ksl_begin(&rtb->ents); + + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + + rtb_on_remove(rtb, ent, cstat); + rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num); + assert(0 == rv); + + rv = rtb_on_pkt_lost_resched_move(rtb, conn, pktns, ent); + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + if (rv != 0) { + return rv; + } + } + + return 0; +} + +void ngtcp2_rtb_remove_early_data(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat) { + ngtcp2_rtb_entry *ent; + ngtcp2_ksl_it it; + int rv; + (void)rv; + + it = ngtcp2_ksl_begin(&rtb->ents); + + for (; !ngtcp2_ksl_it_end(&it);) { + ent = ngtcp2_ksl_it_get(&it); + + if (ent->hd.type != NGTCP2_PKT_0RTT) { + ngtcp2_ksl_it_next(&it); + continue; + } + + rtb_on_remove(rtb, ent, cstat); + rv = ngtcp2_ksl_remove_hint(&rtb->ents, &it, &it, &ent->hd.pkt_num); + assert(0 == rv); + + ngtcp2_rtb_entry_objalloc_del(ent, rtb->rtb_entry_objalloc, + rtb->frc_objalloc, rtb->mem); + } +} + +int ngtcp2_rtb_empty(ngtcp2_rtb *rtb) { + return ngtcp2_ksl_len(&rtb->ents) == 0; +} + +void ngtcp2_rtb_reset_cc_state(ngtcp2_rtb *rtb, int64_t cc_pkt_num) { + rtb->cc_pkt_num = cc_pkt_num; + rtb->cc_bytes_in_flight = 0; +} + +ngtcp2_ssize ngtcp2_rtb_reclaim_on_pto(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, size_t num_pkts) { + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + ngtcp2_ssize reclaimed; + size_t atmost = num_pkts; + + it = ngtcp2_ksl_end(&rtb->ents); + for (; !ngtcp2_ksl_it_begin(&it) && num_pkts >= 1;) { + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + + if ((ent->flags & (NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED | + NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED)) || + !(ent->flags & NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE)) { + continue; + } + + assert(ent->frc); + + reclaimed = + rtb_reclaim_frame(rtb, NGTCP2_RECLAIM_FLAG_NONE, conn, pktns, ent); + if (reclaimed < 0) { + return reclaimed; + } + + /* Mark reclaimed even if reclaimed == 0 so that we can skip it in + the next run. */ + ent->flags |= NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED; + + assert(rtb->num_retransmittable); + --rtb->num_retransmittable; + + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING) { + ent->flags &= (uint16_t)~NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING; + assert(rtb->num_pto_eliciting); + --rtb->num_pto_eliciting; + } + + if (reclaimed) { + --num_pkts; + } + } + + return (ngtcp2_ssize)(atmost - num_pkts); +} diff --git a/lib/ngtcp2_rtb.h b/lib/ngtcp2_rtb.h new file mode 100644 index 0000000..a97805d --- /dev/null +++ b/lib/ngtcp2_rtb.h @@ -0,0 +1,466 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_RTB_H +#define NGTCP2_RTB_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_pkt.h" +#include "ngtcp2_ksl.h" +#include "ngtcp2_pq.h" +#include "ngtcp2_objalloc.h" + +typedef struct ngtcp2_conn ngtcp2_conn; +typedef struct ngtcp2_pktns ngtcp2_pktns; +typedef struct ngtcp2_log ngtcp2_log; +typedef struct ngtcp2_qlog ngtcp2_qlog; +typedef struct ngtcp2_strm ngtcp2_strm; +typedef struct ngtcp2_rst ngtcp2_rst; +typedef struct ngtcp2_cc ngtcp2_cc; + +/* NGTCP2_FRAME_CHAIN_BINDER_FLAG_NONE indicates that no flag is + set. */ +#define NGTCP2_FRAME_CHAIN_BINDER_FLAG_NONE 0x00u +/* NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK indicates that an information + which a frame carries has been acknowledged. */ +#define NGTCP2_FRAME_CHAIN_BINDER_FLAG_ACK 0x01u + +/* + * ngtcp2_frame_chain_binder binds 2 or more of ngtcp2_frame_chain to + * share the acknowledgement state. In general, all + * ngtcp2_frame_chains bound to the same binder must have the same + * information. + */ +typedef struct ngtcp2_frame_chain_binder { + size_t refcount; + /* flags is bitwise OR of zero or more of + NGTCP2_FRAME_CHAIN_BINDER_FLAG_*. */ + uint32_t flags; +} ngtcp2_frame_chain_binder; + +int ngtcp2_frame_chain_binder_new(ngtcp2_frame_chain_binder **pbinder, + const ngtcp2_mem *mem); + +typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; + +/* + * ngtcp2_frame_chain chains frames in a single packet. + */ +struct ngtcp2_frame_chain { + union { + struct { + ngtcp2_frame_chain *next; + ngtcp2_frame_chain_binder *binder; + ngtcp2_frame fr; + }; + + ngtcp2_opl_entry oplent; + }; +}; + +ngtcp2_objalloc_def(frame_chain, ngtcp2_frame_chain, oplent); + +/* + * ngtcp2_bind_frame_chains binds two frame chains |a| and |b| using + * new or existing ngtcp2_frame_chain_binder. |a| might have non-NULL + * a->binder. |b| must not have non-NULL b->binder. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_bind_frame_chains(ngtcp2_frame_chain *a, ngtcp2_frame_chain *b, + const ngtcp2_mem *mem); + +/* NGTCP2_MAX_STREAM_DATACNT is the maximum number of ngtcp2_vec that + a ngtcp2_stream can include. */ +#define NGTCP2_MAX_STREAM_DATACNT 256 + +/* NGTCP2_MAX_CRYPTO_DATACNT is the maximum number of ngtcp2_vec that + a ngtcp2_crypto can include. */ +#define NGTCP2_MAX_CRYPTO_DATACNT 8 + +/* + * ngtcp2_frame_chain_new allocates ngtcp2_frame_chain object and + * assigns its pointer to |*pfrc|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory. + */ +int ngtcp2_frame_chain_new(ngtcp2_frame_chain **pfrc, const ngtcp2_mem *mem); + +/* + * ngtcp2_frame_chain_objalloc_new behaves like + * ngtcp2_frame_chain_new, but it uses |objalloc| to allocate the object. + */ +int ngtcp2_frame_chain_objalloc_new(ngtcp2_frame_chain **pfrc, + ngtcp2_objalloc *objalloc); + +/* + * ngtcp2_frame_chain_extralen_new works like ngtcp2_frame_chain_new, + * but it allocates extra memory |extralen| in order to extend + * ngtcp2_frame. + */ +int ngtcp2_frame_chain_extralen_new(ngtcp2_frame_chain **pfrc, size_t extralen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_frame_chain_stream_datacnt_objalloc_new works like + * ngtcp2_frame_chain_new, but it allocates enough data to store + * additional |datacnt| - 1 ngtcp2_vec object after ngtcp2_stream + * object. If no additional space is required, + * ngtcp2_frame_chain_objalloc_new is called internally. + */ +int ngtcp2_frame_chain_stream_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc, + size_t datacnt, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem); + +/* + * ngtcp2_frame_chain_crypto_datacnt_objalloc_new works like + * ngtcp2_frame_chain_new, but it allocates enough data to store + * additional |datacnt| - 1 ngtcp2_vec object after ngtcp2_crypto + * object. If no additional space is required, + * ngtcp2_frame_chain_objalloc_new is called internally. + */ +int ngtcp2_frame_chain_crypto_datacnt_objalloc_new(ngtcp2_frame_chain **pfrc, + size_t datacnt, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem); + +int ngtcp2_frame_chain_new_token_objalloc_new(ngtcp2_frame_chain **pfrc, + const ngtcp2_vec *token, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem); + +/* + * ngtcp2_frame_chain_del deallocates |frc|. It also deallocates the + * memory pointed by |frc|. + */ +void ngtcp2_frame_chain_del(ngtcp2_frame_chain *frc, const ngtcp2_mem *mem); + +/* + * ngtcp2_frame_chain_objalloc_del adds |frc| to |objalloc| for reuse. + * It might just delete |frc| depending on the frame type and the size + * of |frc|. + */ +void ngtcp2_frame_chain_objalloc_del(ngtcp2_frame_chain *frc, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem); + +/* + * ngtcp2_frame_chain_init initializes |frc|. + */ +void ngtcp2_frame_chain_init(ngtcp2_frame_chain *frc); + +/* + * ngtcp2_frame_chain_list_objalloc_del adds all ngtcp2_frame_chain + * linked from |frc| to |objalloc| for reuse. Depending on the frame type + * and its size, ngtcp2_frame_chain might be deleted instead. + */ +void ngtcp2_frame_chain_list_objalloc_del(ngtcp2_frame_chain *frc, + ngtcp2_objalloc *objalloc, + const ngtcp2_mem *mem); + +/* NGTCP2_RTB_ENTRY_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_RTB_ENTRY_FLAG_NONE 0x00u +/* NGTCP2_RTB_ENTRY_FLAG_PROBE indicates that the entry includes a + probe packet. */ +#define NGTCP2_RTB_ENTRY_FLAG_PROBE 0x01u +/* NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE indicates that the entry + includes a frame which must be retransmitted until it is + acknowledged. In most cases, this flag is used along with + NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING and + NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING. */ +#define NGTCP2_RTB_ENTRY_FLAG_RETRANSMITTABLE 0x02u +/* NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING indicates that the entry + elicits acknowledgement. */ +#define NGTCP2_RTB_ENTRY_FLAG_ACK_ELICITING 0x04u +/* NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED indicates that the packet has + been reclaimed on PTO. It is not marked lost yet and still + consumes congestion window. */ +#define NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED 0x08u +/* NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED indicates that the entry + has been marked lost and, optionally, scheduled to retransmit. */ +#define NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED 0x10u +/* NGTCP2_RTB_ENTRY_FLAG_ECN indicates that the entry is included in a + UDP datagram with ECN marking. */ +#define NGTCP2_RTB_ENTRY_FLAG_ECN 0x20u +/* NGTCP2_RTB_ENTRY_FLAG_DATAGRAM indicates that the entry includes + DATAGRAM frame. */ +#define NGTCP2_RTB_ENTRY_FLAG_DATAGRAM 0x40u +/* NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE indicates that the entry includes + a PMTUD probe packet. */ +#define NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE 0x80u +/* NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING indicates that the entry + includes a packet which elicits PTO probe packets. */ +#define NGTCP2_RTB_ENTRY_FLAG_PTO_ELICITING 0x100u + +typedef struct ngtcp2_rtb_entry ngtcp2_rtb_entry; + +/* + * ngtcp2_rtb_entry is an object stored in ngtcp2_rtb. It corresponds + * to the one packet which is waiting for its ACK. + */ +struct ngtcp2_rtb_entry { + union { + struct { + ngtcp2_rtb_entry *next; + + struct { + int64_t pkt_num; + uint8_t type; + uint8_t flags; + } hd; + ngtcp2_frame_chain *frc; + /* ts is the time point when a packet included in this entry is sent + to a peer. */ + ngtcp2_tstamp ts; + /* lost_ts is the time when this entry is marked lost. */ + ngtcp2_tstamp lost_ts; + /* pktlen is the length of QUIC packet */ + size_t pktlen; + struct { + uint64_t delivered; + ngtcp2_tstamp delivered_ts; + ngtcp2_tstamp first_sent_ts; + uint64_t tx_in_flight; + uint64_t lost; + int is_app_limited; + } rst; + /* flags is bitwise-OR of zero or more of + NGTCP2_RTB_ENTRY_FLAG_*. */ + uint16_t flags; + }; + + ngtcp2_opl_entry oplent; + }; +}; + +ngtcp2_objalloc_def(rtb_entry, ngtcp2_rtb_entry, oplent); + +/* + * ngtcp2_rtb_entry_new allocates ngtcp2_rtb_entry object, and assigns + * its pointer to |*pent|. + */ +int ngtcp2_rtb_entry_objalloc_new(ngtcp2_rtb_entry **pent, + const ngtcp2_pkt_hd *hd, + ngtcp2_frame_chain *frc, ngtcp2_tstamp ts, + size_t pktlen, uint16_t flags, + ngtcp2_objalloc *objalloc); + +/* + * ngtcp2_rtb_entry_objalloc_del adds |ent| to |objalloc| for reuse. + * ngtcp2_frame_chain linked from ent->frc are also added to + * |frc_objalloc| depending on their frame type and size. + */ +void ngtcp2_rtb_entry_objalloc_del(ngtcp2_rtb_entry *ent, + ngtcp2_objalloc *objalloc, + ngtcp2_objalloc *frc_objalloc, + const ngtcp2_mem *mem); + +/* + * ngtcp2_rtb tracks sent packets, and its ACK timeout for + * retransmission. + */ +typedef struct ngtcp2_rtb { + ngtcp2_objalloc *frc_objalloc; + ngtcp2_objalloc *rtb_entry_objalloc; + /* ents includes ngtcp2_rtb_entry sorted by decreasing order of + packet number. */ + ngtcp2_ksl ents; + /* crypto is CRYPTO stream. */ + ngtcp2_strm *crypto; + ngtcp2_rst *rst; + ngtcp2_cc *cc; + ngtcp2_log *log; + ngtcp2_qlog *qlog; + const ngtcp2_mem *mem; + /* largest_acked_tx_pkt_num is the largest packet number + acknowledged by the peer. */ + int64_t largest_acked_tx_pkt_num; + /* num_ack_eliciting is the number of ACK eliciting entries. */ + size_t num_ack_eliciting; + /* num_retransmittable is the number of packets which contain frames + that must be retransmitted on loss. */ + size_t num_retransmittable; + /* num_pto_eliciting is the number of packets that elicit PTO probe + packets. */ + size_t num_pto_eliciting; + /* probe_pkt_left is the number of probe packet to send */ + size_t probe_pkt_left; + /* pktns_id is the identifier of packet number space. */ + ngtcp2_pktns_id pktns_id; + /* cc_pkt_num is the smallest packet number that is contributed to + ngtcp2_conn_stat.bytes_in_flight. */ + int64_t cc_pkt_num; + /* cc_bytes_in_flight is the number of in-flight bytes that is + contributed to ngtcp2_conn_stat.bytes_in_flight. It only + includes the bytes after congestion state is reset. */ + uint64_t cc_bytes_in_flight; + /* persistent_congestion_start_ts is the time when persistent + congestion evaluation is started. It happens roughly after + handshake is confirmed. */ + ngtcp2_tstamp persistent_congestion_start_ts; + /* num_lost_pkts is the number entries in ents which has + NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED flag set. */ + size_t num_lost_pkts; + /* num_lost_pmtud_pkts is the number of entries in ents which have + both NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED and + NGTCP2_RTB_ENTRY_FLAG_PMTUD_PROBE flags set. */ + size_t num_lost_pmtud_pkts; +} ngtcp2_rtb; + +/* + * ngtcp2_rtb_init initializes |rtb|. + */ +void ngtcp2_rtb_init(ngtcp2_rtb *rtb, ngtcp2_pktns_id pktns_id, + ngtcp2_strm *crypto, ngtcp2_rst *rst, ngtcp2_cc *cc, + ngtcp2_log *log, ngtcp2_qlog *qlog, + ngtcp2_objalloc *rtb_entry_objalloc, + ngtcp2_objalloc *frc_objalloc, const ngtcp2_mem *mem); + +/* + * ngtcp2_rtb_free deallocates resources allocated for |rtb|. + */ +void ngtcp2_rtb_free(ngtcp2_rtb *rtb); + +/* + * ngtcp2_rtb_add adds |ent| to |rtb|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_rtb_add(ngtcp2_rtb *rtb, ngtcp2_rtb_entry *ent, + ngtcp2_conn_stat *cstat); + +/* + * ngtcp2_rtb_head returns the iterator which points to the entry + * which has the largest packet number. If there is no entry, + * returned value satisfies ngtcp2_ksl_it_end(&it) != 0. + */ +ngtcp2_ksl_it ngtcp2_rtb_head(ngtcp2_rtb *rtb); + +/* + * ngtcp2_rtb_recv_ack removes acked ngtcp2_rtb_entry from |rtb|. + * |pkt_num| is a packet number which includes |fr|. |pkt_ts| is the + * timestamp when packet is received. |ts| should be the current + * time. Usually they are the same, but for buffered packets, + * |pkt_ts| would be earlier than |ts|. + * + * This function returns the number of newly acknowledged packets if + * it succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_CALLBACK_FAILURE + * User callback failed + * NGTCP2_ERR_NOMEM + * Out of memory + */ +ngtcp2_ssize ngtcp2_rtb_recv_ack(ngtcp2_rtb *rtb, const ngtcp2_ack *fr, + ngtcp2_conn_stat *cstat, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_tstamp pkt_ts, + ngtcp2_tstamp ts); + +/* + * ngtcp2_rtb_detect_lost_pkt detects lost packets and prepends the + * frames contained them to |*pfrc|. Even when this function fails, + * some frames might be prepended to |*pfrc| and the caller should + * handle them. + */ +int ngtcp2_rtb_detect_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat, + ngtcp2_tstamp ts); + +/* + * ngtcp2_rtb_remove_expired_lost_pkt removes expired lost packet. + */ +void ngtcp2_rtb_remove_expired_lost_pkt(ngtcp2_rtb *rtb, ngtcp2_duration pto, + ngtcp2_tstamp ts); + +/* + * ngtcp2_rtb_lost_pkt_ts returns the earliest time when the still + * retained packet was lost. It returns UINT64_MAX if no such packet + * exists. + */ +ngtcp2_tstamp ngtcp2_rtb_lost_pkt_ts(ngtcp2_rtb *rtb); + +/* + * ngtcp2_rtb_remove_all removes all packets from |rtb| and prepends + * all frames to |*pfrc|. Even when this function fails, some frames + * might be prepended to |*pfrc| and the caller should handle them. + */ +int ngtcp2_rtb_remove_all(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, ngtcp2_conn_stat *cstat); + +/* + * ngtcp2_rtb_remove_early_data removes all entries for 0RTT packets. + */ +void ngtcp2_rtb_remove_early_data(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat); + +/* + * ngtcp2_rtb_empty returns nonzero if |rtb| have no entry. + */ +int ngtcp2_rtb_empty(ngtcp2_rtb *rtb); + +/* + * ngtcp2_rtb_reset_cc_state resets congestion state in |rtb|. + * |cc_pkt_num| is the next outbound packet number which is sent under + * new congestion state. + */ +void ngtcp2_rtb_reset_cc_state(ngtcp2_rtb *rtb, int64_t cc_pkt_num); + +/* + * ngtcp2_rtb_remove_expired_lost_pkt ensures that the number of lost + * packets at most |n|. + */ +void ngtcp2_rtb_remove_excessive_lost_pkt(ngtcp2_rtb *rtb, size_t n); + +/* + * ngtcp2_rtb_reclaim_on_pto reclaims up to |num_pkts| packets which + * are in-flight and not marked lost to send them in PTO probe. The + * reclaimed frames are chained to |*pfrc|. + * + * This function returns the number of packets reclaimed if it + * succeeds, or one of the following negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +ngtcp2_ssize ngtcp2_rtb_reclaim_on_pto(ngtcp2_rtb *rtb, ngtcp2_conn *conn, + ngtcp2_pktns *pktns, size_t num_pkts); + +#endif /* NGTCP2_RTB_H */ diff --git a/lib/ngtcp2_str.c b/lib/ngtcp2_str.c new file mode 100644 index 0000000..a61636d --- /dev/null +++ b/lib/ngtcp2_str.c @@ -0,0 +1,233 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_str.h" + +#include <string.h> + +#include "ngtcp2_macro.h" + +void *ngtcp2_cpymem(void *dest, const void *src, size_t n) { + memcpy(dest, src, n); + return (uint8_t *)dest + n; +} + +uint8_t *ngtcp2_setmem(uint8_t *dest, uint8_t b, size_t n) { + memset(dest, b, n); + return dest + n; +} + +const void *ngtcp2_get_bytes(void *dest, const void *src, size_t n) { + memcpy(dest, src, n); + return (uint8_t *)src + n; +} + +#define LOWER_XDIGITS "0123456789abcdef" + +uint8_t *ngtcp2_encode_hex(uint8_t *dest, const uint8_t *data, size_t len) { + size_t i; + uint8_t *p = dest; + + for (i = 0; i < len; ++i) { + *p++ = (uint8_t)LOWER_XDIGITS[data[i] >> 4]; + *p++ = (uint8_t)LOWER_XDIGITS[data[i] & 0xf]; + } + + *p = '\0'; + + return dest; +} + +char *ngtcp2_encode_printable_ascii(char *dest, const uint8_t *data, + size_t len) { + size_t i; + char *p = dest; + uint8_t c; + + for (i = 0; i < len; ++i) { + c = data[i]; + if (0x20 <= c && c <= 0x7e) { + *p++ = (char)c; + } else { + *p++ = '.'; + } + } + + *p = '\0'; + + return dest; +} + +/* + * write_uint writes |n| to the buffer pointed by |p| in decimal + * representation. It returns |p| plus the number of bytes written. + * The function assumes that the buffer has enough capacity to contain + * a string. + */ +static uint8_t *write_uint(uint8_t *p, uint64_t n) { + size_t nlen = 0; + uint64_t t; + uint8_t *res; + + if (n == 0) { + *p++ = '0'; + return p; + } + for (t = n; t; t /= 10, ++nlen) + ; + p += nlen; + res = p; + for (; n; n /= 10) { + *--p = (uint8_t)((n % 10) + '0'); + } + return res; +} + +uint8_t *ngtcp2_encode_ipv4(uint8_t *dest, const uint8_t *addr) { + size_t i; + uint8_t *p = dest; + + p = write_uint(p, addr[0]); + + for (i = 1; i < 4; ++i) { + *p++ = '.'; + p = write_uint(p, addr[i]); + } + + *p = '\0'; + + return dest; +} + +/* + * write_hex_zsup writes the content of buffer pointed by |data| of + * length |len| to |dest| in hex string. Any leading zeros are + * suppressed. It returns |dest| plus the number of bytes written. + */ +static uint8_t *write_hex_zsup(uint8_t *dest, const uint8_t *data, size_t len) { + size_t i; + uint8_t *p = dest; + uint8_t d; + + for (i = 0; i < len; ++i) { + d = data[i]; + if (d >> 4) { + break; + } + + d &= 0xf; + + if (d) { + *p++ = (uint8_t)LOWER_XDIGITS[d]; + ++i; + break; + } + } + + if (p == dest && i == len) { + *p++ = '0'; + return p; + } + + for (; i < len; ++i) { + d = data[i]; + *p++ = (uint8_t)LOWER_XDIGITS[d >> 4]; + *p++ = (uint8_t)LOWER_XDIGITS[d & 0xf]; + } + + return p; +} + +uint8_t *ngtcp2_encode_ipv6(uint8_t *dest, const uint8_t *addr) { + uint16_t blks[8]; + size_t i; + size_t zlen, zoff; + size_t max_zlen = 0, max_zoff = 8; + uint8_t *p = dest; + + for (i = 0; i < 16; i += sizeof(uint16_t)) { + /* Copy in network byte order. */ + memcpy(&blks[i / sizeof(uint16_t)], addr + i, sizeof(uint16_t)); + } + + for (i = 0; i < 8;) { + if (blks[i]) { + ++i; + continue; + } + + zlen = 1; + zoff = i; + + ++i; + for (; i < 8 && blks[i] == 0; ++i, ++zlen) + ; + if (zlen > max_zlen) { + max_zlen = zlen; + max_zoff = zoff; + } + } + + /* Do not suppress a single '0' block */ + if (max_zlen == 1) { + max_zoff = 8; + } + + if (max_zoff != 0) { + p = write_hex_zsup(p, (const uint8_t *)blks, sizeof(uint16_t)); + + for (i = 1; i < max_zoff; ++i) { + *p++ = ':'; + p = write_hex_zsup(p, (const uint8_t *)(blks + i), sizeof(uint16_t)); + } + } + + if (max_zoff != 8) { + *p++ = ':'; + + if (max_zoff + max_zlen == 8) { + *p++ = ':'; + } else { + for (i = max_zoff + max_zlen; i < 8; ++i) { + *p++ = ':'; + p = write_hex_zsup(p, (const uint8_t *)(blks + i), sizeof(uint16_t)); + } + } + } + + *p = '\0'; + + return dest; +} + +int ngtcp2_cmemeq(const uint8_t *a, const uint8_t *b, size_t n) { + size_t i; + int rv = 0; + + for (i = 0; i < n; ++i) { + rv |= a[i] ^ b[i]; + } + + return rv == 0; +} diff --git a/lib/ngtcp2_str.h b/lib/ngtcp2_str.h new file mode 100644 index 0000000..deb75e3 --- /dev/null +++ b/lib/ngtcp2_str.h @@ -0,0 +1,94 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_STR_H +#define NGTCP2_STR_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +void *ngtcp2_cpymem(void *dest, const void *src, size_t n); + +/* + * ngtcp2_setmem writes a string of length |n| consisting only |b| to + * the buffer pointed by |dest|. It returns dest + n; + */ +uint8_t *ngtcp2_setmem(uint8_t *dest, uint8_t b, size_t n); + +/* + * ngtcp2_get_bytes copies |n| bytes from |src| to |dest|, and returns + * |src| + |n|. + */ +const void *ngtcp2_get_bytes(void *dest, const void *src, size_t n); + +/* + * ngtcp2_encode_hex encodes |data| of length |len| in hex string. It + * writes additional NULL bytes at the end of the buffer. The buffer + * pointed by |dest| must have at least |len| * 2 + 1 bytes space. + * This function returns |dest|. + */ +uint8_t *ngtcp2_encode_hex(uint8_t *dest, const uint8_t *data, size_t len); + +/* + * ngtcp2_encode_ipv4 encodes binary form IPv4 address stored in + * |addr| to human readable text form in the buffer pointed by |dest|. + * The capacity of buffer must have enough length to store a text form + * plus a terminating NULL byte. The resulting text form ends with + * NULL byte. The function returns |dest|. + */ +uint8_t *ngtcp2_encode_ipv4(uint8_t *dest, const uint8_t *addr); + +/* + * ngtcp2_encode_ipv6 encodes binary form IPv6 address stored in + * |addr| to human readable text form in the buffer pointed by |dest|. + * The capacity of buffer must have enough length to store a text form + * plus a terminating NULL byte. The resulting text form ends with + * NULL byte. The function produces the canonical form of IPv6 text + * representation described in + * https://tools.ietf.org/html/rfc5952#section-4. The function + * returns |dest|. + */ +uint8_t *ngtcp2_encode_ipv6(uint8_t *dest, const uint8_t *addr); + +/* + * ngtcp2_encode_printable_ascii encodes |data| of length |len| in + * |dest| in the following manner: printable ascii characters are + * copied as is. The other characters are converted to ".". It + * writes additional NULL bytes at the end of the buffer. |dest| must + * have at least |len| + 1 bytes. This function returns |dest|. + */ +char *ngtcp2_encode_printable_ascii(char *dest, const uint8_t *data, + size_t len); + +/* + * ngtcp2_cmemeq returns nonzero if the first |n| bytes of the buffers + * pointed by |a| and |b| are equal. The comparison is done in a + * constant time manner. + */ +int ngtcp2_cmemeq(const uint8_t *a, const uint8_t *b, size_t n); + +#endif /* NGTCP2_STR_H */ diff --git a/lib/ngtcp2_strm.c b/lib/ngtcp2_strm.c new file mode 100644 index 0000000..6f20e86 --- /dev/null +++ b/lib/ngtcp2_strm.c @@ -0,0 +1,698 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_strm.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_rtb.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_vec.h" + +static int offset_less(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) { + return *(int64_t *)lhs < *(int64_t *)rhs; +} + +void ngtcp2_strm_init(ngtcp2_strm *strm, int64_t stream_id, uint32_t flags, + uint64_t max_rx_offset, uint64_t max_tx_offset, + void *stream_user_data, ngtcp2_objalloc *frc_objalloc, + const ngtcp2_mem *mem) { + strm->frc_objalloc = frc_objalloc; + strm->cycle = 0; + strm->tx.acked_offset = NULL; + strm->tx.cont_acked_offset = 0; + strm->tx.streamfrq = NULL; + strm->tx.offset = 0; + strm->tx.max_offset = max_tx_offset; + strm->tx.last_max_stream_data_ts = UINT64_MAX; + strm->tx.loss_count = 0; + strm->tx.last_lost_pkt_num = -1; + strm->rx.rob = NULL; + strm->rx.cont_offset = 0; + strm->rx.last_offset = 0; + strm->stream_id = stream_id; + strm->flags = flags; + strm->stream_user_data = stream_user_data; + strm->rx.window = strm->rx.max_offset = strm->rx.unsent_max_offset = + max_rx_offset; + strm->pe.index = NGTCP2_PQ_BAD_INDEX; + strm->mem = mem; + strm->app_error_code = 0; +} + +void ngtcp2_strm_free(ngtcp2_strm *strm) { + ngtcp2_ksl_it it; + + if (strm == NULL) { + return; + } + + if (strm->tx.streamfrq) { + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + ngtcp2_frame_chain_objalloc_del(ngtcp2_ksl_it_get(&it), + strm->frc_objalloc, strm->mem); + } + + ngtcp2_ksl_free(strm->tx.streamfrq); + ngtcp2_mem_free(strm->mem, strm->tx.streamfrq); + } + + if (strm->rx.rob) { + ngtcp2_rob_free(strm->rx.rob); + ngtcp2_mem_free(strm->mem, strm->rx.rob); + } + + if (strm->tx.acked_offset) { + ngtcp2_gaptr_free(strm->tx.acked_offset); + ngtcp2_mem_free(strm->mem, strm->tx.acked_offset); + } +} + +static int strm_rob_init(ngtcp2_strm *strm) { + int rv; + ngtcp2_rob *rob = ngtcp2_mem_malloc(strm->mem, sizeof(*rob)); + + if (rob == NULL) { + return NGTCP2_ERR_NOMEM; + } + + rv = ngtcp2_rob_init(rob, 8 * 1024, strm->mem); + if (rv != 0) { + ngtcp2_mem_free(strm->mem, rob); + return rv; + } + + strm->rx.rob = rob; + + return 0; +} + +uint64_t ngtcp2_strm_rx_offset(ngtcp2_strm *strm) { + if (strm->rx.rob == NULL) { + return strm->rx.cont_offset; + } + return ngtcp2_rob_first_gap_offset(strm->rx.rob); +} + +/* strm_rob_heavily_fragmented returns nonzero if the number of gaps + in |rob| exceeds the limit. */ +static int strm_rob_heavily_fragmented(ngtcp2_rob *rob) { + return ngtcp2_ksl_len(&rob->gapksl) >= 1000; +} + +int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, + size_t datalen, uint64_t offset) { + int rv; + + if (strm->rx.rob == NULL) { + rv = strm_rob_init(strm); + if (rv != 0) { + return rv; + } + + if (strm->rx.cont_offset) { + rv = ngtcp2_rob_remove_prefix(strm->rx.rob, strm->rx.cont_offset); + if (rv != 0) { + return rv; + } + } + } + + if (strm_rob_heavily_fragmented(strm->rx.rob)) { + return NGTCP2_ERR_INTERNAL; + } + + return ngtcp2_rob_push(strm->rx.rob, offset, data, datalen); +} + +int ngtcp2_strm_update_rx_offset(ngtcp2_strm *strm, uint64_t offset) { + if (strm->rx.rob == NULL) { + strm->rx.cont_offset = offset; + return 0; + } + + return ngtcp2_rob_remove_prefix(strm->rx.rob, offset); +} + +void ngtcp2_strm_shutdown(ngtcp2_strm *strm, uint32_t flags) { + strm->flags |= flags & NGTCP2_STRM_FLAG_SHUT_RDWR; +} + +static int strm_streamfrq_init(ngtcp2_strm *strm) { + ngtcp2_ksl *streamfrq = ngtcp2_mem_malloc(strm->mem, sizeof(*streamfrq)); + if (streamfrq == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_ksl_init(streamfrq, offset_less, sizeof(uint64_t), strm->mem); + + strm->tx.streamfrq = streamfrq; + + return 0; +} + +int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm, ngtcp2_frame_chain *frc) { + int rv; + + assert(frc->fr.type == NGTCP2_FRAME_STREAM); + assert(frc->next == NULL); + + if (strm->tx.streamfrq == NULL) { + rv = strm_streamfrq_init(strm); + if (rv != 0) { + return rv; + } + } + + return ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &frc->fr.stream.offset, + frc); +} + +static int strm_streamfrq_unacked_pop(ngtcp2_strm *strm, + ngtcp2_frame_chain **pfrc) { + ngtcp2_frame_chain *frc, *nfrc; + ngtcp2_stream *fr, *nfr; + uint64_t offset, end_offset; + size_t idx, end_idx; + uint64_t base_offset, end_base_offset; + ngtcp2_range gap; + ngtcp2_vec *v; + int rv; + ngtcp2_ksl_it it; + + *pfrc = NULL; + + assert(strm->tx.streamfrq); + assert(ngtcp2_ksl_len(strm->tx.streamfrq)); + + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it);) { + frc = ngtcp2_ksl_it_get(&it); + fr = &frc->fr.stream; + + ngtcp2_ksl_remove_hint(strm->tx.streamfrq, &it, &it, &fr->offset); + + idx = 0; + offset = fr->offset; + base_offset = 0; + + gap = ngtcp2_strm_get_unacked_range_after(strm, offset); + if (gap.begin < offset) { + gap.begin = offset; + } + + for (; idx < fr->datacnt && offset < gap.begin; ++idx) { + v = &fr->data[idx]; + if (offset + v->len > gap.begin) { + base_offset = gap.begin - offset; + break; + } + + offset += v->len; + } + + if (idx == fr->datacnt) { + if (fr->fin) { + if (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED) { + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + assert(ngtcp2_ksl_len(strm->tx.streamfrq) == 0); + return 0; + } + + fr->offset += ngtcp2_vec_len(fr->data, fr->datacnt); + fr->datacnt = 0; + + *pfrc = frc; + + return 0; + } + + if (fr->offset == 0 && fr->datacnt == 0 && strm->tx.offset == 0 && + !(strm->flags & NGTCP2_STRM_FLAG_ANY_ACKED)) { + *pfrc = frc; + + return 0; + } + + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + continue; + } + + assert(gap.begin == offset + base_offset); + + end_idx = idx; + end_offset = offset; + end_base_offset = 0; + + for (; end_idx < fr->datacnt; ++end_idx) { + v = &fr->data[end_idx]; + if (end_offset + v->len > gap.end) { + end_base_offset = gap.end - end_offset; + break; + } + + end_offset += v->len; + } + + if (fr->offset == offset && base_offset == 0 && fr->datacnt == end_idx) { + *pfrc = frc; + return 0; + } + + if (fr->datacnt == end_idx) { + memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx)); + + assert(fr->data[0].len > base_offset); + + fr->offset = offset + base_offset; + fr->datacnt = end_idx - idx; + fr->data[0].base += base_offset; + fr->data[0].len -= (size_t)base_offset; + + *pfrc = frc; + return 0; + } + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, fr->datacnt - end_idx, strm->frc_objalloc, strm->mem); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + + nfr = &nfrc->fr.stream; + memcpy(nfr->data, fr->data + end_idx, + sizeof(nfr->data[0]) * (fr->datacnt - end_idx)); + + assert(nfr->data[0].len > end_base_offset); + + nfr->type = NGTCP2_FRAME_STREAM; + nfr->flags = 0; + nfr->fin = fr->fin; + nfr->stream_id = fr->stream_id; + nfr->offset = end_offset + end_base_offset; + nfr->datacnt = fr->datacnt - end_idx; + nfr->data[0].base += end_base_offset; + nfr->data[0].len -= (size_t)end_base_offset; + + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + + if (end_base_offset) { + ++end_idx; + } + + memmove(fr->data, fr->data + idx, sizeof(fr->data[0]) * (end_idx - idx)); + + assert(fr->data[0].len > base_offset); + + fr->fin = 0; + fr->offset = offset + base_offset; + fr->datacnt = end_idx - idx; + if (end_base_offset) { + assert(fr->data[fr->datacnt - 1].len > end_base_offset); + fr->data[fr->datacnt - 1].len = (size_t)end_base_offset; + } + fr->data[0].base += base_offset; + fr->data[0].len -= (size_t)base_offset; + + *pfrc = frc; + return 0; + } + + return 0; +} + +int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + size_t left) { + ngtcp2_stream *fr, *nfr; + ngtcp2_frame_chain *frc, *nfrc; + int rv; + size_t nmerged; + uint64_t datalen; + ngtcp2_vec a[NGTCP2_MAX_STREAM_DATACNT]; + ngtcp2_vec b[NGTCP2_MAX_STREAM_DATACNT]; + size_t acnt, bcnt; + uint64_t unacked_offset; + + if (strm->tx.streamfrq == NULL || ngtcp2_ksl_len(strm->tx.streamfrq) == 0) { + *pfrc = NULL; + return 0; + } + + rv = strm_streamfrq_unacked_pop(strm, &frc); + if (rv != 0) { + return rv; + } + if (frc == NULL) { + *pfrc = NULL; + return 0; + } + + fr = &frc->fr.stream; + datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + + if (left == 0) { + /* datalen could be zero if 0 length STREAM has been sent */ + if (datalen || ngtcp2_ksl_len(strm->tx.streamfrq) > 1) { + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &fr->offset, frc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + *pfrc = NULL; + return 0; + } + } + + if (datalen > left) { + ngtcp2_vec_copy(a, fr->data, fr->datacnt); + acnt = fr->datacnt; + + bcnt = 0; + ngtcp2_vec_split(a, &acnt, b, &bcnt, left, NGTCP2_MAX_STREAM_DATACNT); + + assert(acnt > 0); + assert(bcnt > 0); + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, bcnt, strm->frc_objalloc, strm->mem); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + + nfr = &nfrc->fr.stream; + nfr->type = NGTCP2_FRAME_STREAM; + nfr->flags = 0; + nfr->fin = fr->fin; + nfr->stream_id = fr->stream_id; + nfr->offset = fr->offset + left; + nfr->datacnt = bcnt; + ngtcp2_vec_copy(nfr->data, b, bcnt); + + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, acnt, strm->frc_objalloc, strm->mem); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + + nfr = &nfrc->fr.stream; + *nfr = *fr; + nfr->fin = 0; + nfr->datacnt = acnt; + ngtcp2_vec_copy(nfr->data, a, acnt); + + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + + *pfrc = nfrc; + + return 0; + } + + left -= (size_t)datalen; + + ngtcp2_vec_copy(a, fr->data, fr->datacnt); + acnt = fr->datacnt; + + for (; left && ngtcp2_ksl_len(strm->tx.streamfrq);) { + unacked_offset = ngtcp2_strm_streamfrq_unacked_offset(strm); + if (unacked_offset != fr->offset + datalen) { + assert(fr->offset + datalen < unacked_offset); + break; + } + + rv = strm_streamfrq_unacked_pop(strm, &nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + if (nfrc == NULL) { + break; + } + + nfr = &nfrc->fr.stream; + + if (nfr->fin && nfr->datacnt == 0) { + fr->fin = 1; + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + break; + } + + nmerged = ngtcp2_vec_merge(a, &acnt, nfr->data, &nfr->datacnt, left, + NGTCP2_MAX_STREAM_DATACNT); + if (nmerged == 0) { + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + assert(ngtcp2_err_is_fatal(rv)); + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + break; + } + + datalen += nmerged; + left -= nmerged; + + if (nfr->datacnt == 0) { + fr->fin = nfr->fin; + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + continue; + } + + nfr->offset += nmerged; + + rv = ngtcp2_ksl_insert(strm->tx.streamfrq, NULL, &nfr->offset, nfrc); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(nfrc, strm->frc_objalloc, strm->mem); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + + break; + } + + if (acnt == fr->datacnt) { + if (acnt > 0) { + fr->data[acnt - 1] = a[acnt - 1]; + } + + *pfrc = frc; + return 0; + } + + assert(acnt > fr->datacnt); + + rv = ngtcp2_frame_chain_stream_datacnt_objalloc_new( + &nfrc, acnt, strm->frc_objalloc, strm->mem); + if (rv != 0) { + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + return rv; + } + + nfr = &nfrc->fr.stream; + *nfr = *fr; + nfr->datacnt = acnt; + ngtcp2_vec_copy(nfr->data, a, acnt); + + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + + *pfrc = nfrc; + + return 0; +} + +uint64_t ngtcp2_strm_streamfrq_unacked_offset(ngtcp2_strm *strm) { + ngtcp2_frame_chain *frc; + ngtcp2_stream *fr; + ngtcp2_range gap; + ngtcp2_ksl_it it; + uint64_t datalen; + + assert(strm->tx.streamfrq); + assert(ngtcp2_ksl_len(strm->tx.streamfrq)); + + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + frc = ngtcp2_ksl_it_get(&it); + fr = &frc->fr.stream; + + gap = ngtcp2_strm_get_unacked_range_after(strm, fr->offset); + + datalen = ngtcp2_vec_len(fr->data, fr->datacnt); + + if (gap.begin <= fr->offset) { + return fr->offset; + } + if (gap.begin < fr->offset + datalen) { + return gap.begin; + } + if (fr->offset + datalen == gap.begin && fr->fin && + !(strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED)) { + return fr->offset + datalen; + } + } + + return (uint64_t)-1; +} + +ngtcp2_frame_chain *ngtcp2_strm_streamfrq_top(ngtcp2_strm *strm) { + ngtcp2_ksl_it it; + + assert(strm->tx.streamfrq); + assert(ngtcp2_ksl_len(strm->tx.streamfrq)); + + it = ngtcp2_ksl_begin(strm->tx.streamfrq); + return ngtcp2_ksl_it_get(&it); +} + +int ngtcp2_strm_streamfrq_empty(ngtcp2_strm *strm) { + return strm->tx.streamfrq == NULL || ngtcp2_ksl_len(strm->tx.streamfrq) == 0; +} + +void ngtcp2_strm_streamfrq_clear(ngtcp2_strm *strm) { + ngtcp2_frame_chain *frc; + ngtcp2_ksl_it it; + + if (strm->tx.streamfrq == NULL) { + return; + } + + for (it = ngtcp2_ksl_begin(strm->tx.streamfrq); !ngtcp2_ksl_it_end(&it); + ngtcp2_ksl_it_next(&it)) { + frc = ngtcp2_ksl_it_get(&it); + ngtcp2_frame_chain_objalloc_del(frc, strm->frc_objalloc, strm->mem); + } + ngtcp2_ksl_clear(strm->tx.streamfrq); +} + +int ngtcp2_strm_is_tx_queued(ngtcp2_strm *strm) { + return strm->pe.index != NGTCP2_PQ_BAD_INDEX; +} + +int ngtcp2_strm_is_all_tx_data_acked(ngtcp2_strm *strm) { + if (strm->tx.acked_offset == NULL) { + return strm->tx.cont_acked_offset == strm->tx.offset; + } + + return ngtcp2_gaptr_first_gap_offset(strm->tx.acked_offset) == + strm->tx.offset; +} + +int ngtcp2_strm_is_all_tx_data_fin_acked(ngtcp2_strm *strm) { + return (strm->flags & NGTCP2_STRM_FLAG_FIN_ACKED) && + ngtcp2_strm_is_all_tx_data_acked(strm); +} + +ngtcp2_range ngtcp2_strm_get_unacked_range_after(ngtcp2_strm *strm, + uint64_t offset) { + ngtcp2_range gap; + + if (strm->tx.acked_offset == NULL) { + gap.begin = strm->tx.cont_acked_offset; + gap.end = UINT64_MAX; + return gap; + } + + return ngtcp2_gaptr_get_first_gap_after(strm->tx.acked_offset, offset); +} + +uint64_t ngtcp2_strm_get_acked_offset(ngtcp2_strm *strm) { + if (strm->tx.acked_offset == NULL) { + return strm->tx.cont_acked_offset; + } + + return ngtcp2_gaptr_first_gap_offset(strm->tx.acked_offset); +} + +static int strm_acked_offset_init(ngtcp2_strm *strm) { + ngtcp2_gaptr *acked_offset = + ngtcp2_mem_malloc(strm->mem, sizeof(*acked_offset)); + + if (acked_offset == NULL) { + return NGTCP2_ERR_NOMEM; + } + + ngtcp2_gaptr_init(acked_offset, strm->mem); + + strm->tx.acked_offset = acked_offset; + + return 0; +} + +int ngtcp2_strm_ack_data(ngtcp2_strm *strm, uint64_t offset, uint64_t len) { + int rv; + + if (strm->tx.acked_offset == NULL) { + if (strm->tx.cont_acked_offset == offset) { + strm->tx.cont_acked_offset += len; + return 0; + } + + rv = strm_acked_offset_init(strm); + if (rv != 0) { + return rv; + } + + rv = + ngtcp2_gaptr_push(strm->tx.acked_offset, 0, strm->tx.cont_acked_offset); + if (rv != 0) { + return rv; + } + } + + return ngtcp2_gaptr_push(strm->tx.acked_offset, offset, len); +} + +void ngtcp2_strm_set_app_error_code(ngtcp2_strm *strm, + uint64_t app_error_code) { + if (strm->flags & NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET) { + return; + } + + assert(0 == strm->app_error_code); + + strm->flags |= NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET; + strm->app_error_code = app_error_code; +} diff --git a/lib/ngtcp2_strm.h b/lib/ngtcp2_strm.h new file mode 100644 index 0000000..8e3cfe8 --- /dev/null +++ b/lib/ngtcp2_strm.h @@ -0,0 +1,310 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_STRM_H +#define NGTCP2_STRM_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_rob.h" +#include "ngtcp2_map.h" +#include "ngtcp2_gaptr.h" +#include "ngtcp2_ksl.h" +#include "ngtcp2_pq.h" + +typedef struct ngtcp2_frame_chain ngtcp2_frame_chain; + +/* NGTCP2_STRM_FLAG_NONE indicates that no flag is set. */ +#define NGTCP2_STRM_FLAG_NONE 0x00u +/* NGTCP2_STRM_FLAG_SHUT_RD indicates that further reception of stream + data is not allowed. */ +#define NGTCP2_STRM_FLAG_SHUT_RD 0x01u +/* NGTCP2_STRM_FLAG_SHUT_WR indicates that further transmission of + stream data is not allowed. */ +#define NGTCP2_STRM_FLAG_SHUT_WR 0x02u +#define NGTCP2_STRM_FLAG_SHUT_RDWR \ + (NGTCP2_STRM_FLAG_SHUT_RD | NGTCP2_STRM_FLAG_SHUT_WR) +/* NGTCP2_STRM_FLAG_SENT_RST indicates that RST_STREAM is sent from + the local endpoint. In this case, NGTCP2_STRM_FLAG_SHUT_WR is also + set. */ +#define NGTCP2_STRM_FLAG_SENT_RST 0x04u +/* NGTCP2_STRM_FLAG_SENT_RST indicates that RST_STREAM is received + from the remote endpoint. In this case, NGTCP2_STRM_FLAG_SHUT_RD + is also set. */ +#define NGTCP2_STRM_FLAG_RECV_RST 0x08u +/* NGTCP2_STRM_FLAG_STOP_SENDING indicates that STOP_SENDING is sent + from the local endpoint. */ +#define NGTCP2_STRM_FLAG_STOP_SENDING 0x10u +/* NGTCP2_STRM_FLAG_RST_ACKED indicates that the outgoing RST_STREAM + is acknowledged by peer. */ +#define NGTCP2_STRM_FLAG_RST_ACKED 0x20u +/* NGTCP2_STRM_FLAG_FIN_ACKED indicates that a STREAM with FIN bit set + is acknowledged by a remote endpoint. */ +#define NGTCP2_STRM_FLAG_FIN_ACKED 0x40u +/* NGTCP2_STRM_FLAG_ANY_ACKED indicates that any portion of stream + data, including 0 length segment, is acknowledged. */ +#define NGTCP2_STRM_FLAG_ANY_ACKED 0x80u +/* NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET indicates that app_error_code + field is set. This resolves the ambiguity that the initial + app_error_code value 0 might be a proper application error code. + In this case, without this flag, we are unable to distinguish + assigned value from unassigned one. */ +#define NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET 0x100u +/* NGTCP2_STRM_FLAG_STREAM_STOP_SENDING_CALLED is set when + stream_stop_sending callback is called. */ +#define NGTCP2_STRM_FLAG_STREAM_STOP_SENDING_CALLED 0x200u + +typedef struct ngtcp2_strm ngtcp2_strm; + +struct ngtcp2_strm { + union { + struct { + ngtcp2_pq_entry pe; + uint64_t cycle; + ngtcp2_objalloc *frc_objalloc; + + struct { + /* acked_offset tracks acknowledged outgoing data. */ + ngtcp2_gaptr *acked_offset; + /* cont_acked_offset is the offset that all data up to this offset + is acknowledged by a remote endpoint. It is used until the + remote endpoint acknowledges data in out-of-order. After that, + acked_offset is used instead. */ + uint64_t cont_acked_offset; + /* streamfrq contains STREAM frame for retransmission. The flow + control credits have been paid when they are transmitted first + time. There are no restriction regarding flow control for + retransmission. */ + ngtcp2_ksl *streamfrq; + /* offset is the next offset of outgoing data. In other words, it + is the number of bytes sent in this stream without + duplication. */ + uint64_t offset; + /* max_tx_offset is the maximum offset that local endpoint can + send for this stream. */ + uint64_t max_offset; + /* last_max_stream_data_ts is the timestamp when last + MAX_STREAM_DATA frame is sent. */ + ngtcp2_tstamp last_max_stream_data_ts; + /* loss_count is the number of packets that contain STREAM + frame for this stream and are declared to be lost. It may + include the spurious losses. It does not include a packet + whose contents have been reclaimed for PTO and which is + later declared to be lost. Those data are not blocked by + the flow control and will be sent immediately if no other + restrictions are applied. */ + size_t loss_count; + /* last_lost_pkt_num is the packet number of the packet that + is counted to loss_count. It is used to avoid to count + multiple STREAM frames in one lost packet. */ + int64_t last_lost_pkt_num; + } tx; + + struct { + /* rob is the reorder buffer for incoming stream data. The data + received in out of order is buffered and sorted by its offset + in this object. */ + ngtcp2_rob *rob; + /* cont_offset is the largest offset of consecutive data. It is + used until the endpoint receives out-of-order data. After + that, rob is used to track the offset and data. */ + uint64_t cont_offset; + /* last_offset is the largest offset of stream data received for + this stream. */ + uint64_t last_offset; + /* max_offset is the maximum offset that remote endpoint can send + to this stream. */ + uint64_t max_offset; + /* unsent_max_offset is the maximum offset that remote endpoint + can send to this stream, and it is not notified to the remote + endpoint. unsent_max_offset >= max_offset must be hold. */ + uint64_t unsent_max_offset; + /* window is the stream-level flow control window size. */ + uint64_t window; + } rx; + + const ngtcp2_mem *mem; + int64_t stream_id; + void *stream_user_data; + /* flags is bit-wise OR of zero or more of NGTCP2_STRM_FLAG_*. */ + uint32_t flags; + /* app_error_code is an error code the local endpoint sent in + RESET_STREAM or STOP_SENDING, or received from a remote endpoint + in RESET_STREAM or STOP_SENDING. First application error code is + chosen and when set, NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET flag is + set in flags field. */ + uint64_t app_error_code; + }; + + ngtcp2_opl_entry oplent; + }; +}; + +/* + * ngtcp2_strm_init initializes |strm|. + */ +void ngtcp2_strm_init(ngtcp2_strm *strm, int64_t stream_id, uint32_t flags, + uint64_t max_rx_offset, uint64_t max_tx_offset, + void *stream_user_data, ngtcp2_objalloc *frc_objalloc, + const ngtcp2_mem *mem); + +/* + * ngtcp2_strm_free deallocates memory allocated for |strm|. This + * function does not free the memory pointed by |strm| itself. + */ +void ngtcp2_strm_free(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_rx_offset returns the minimum offset of stream data + * which is not received yet. + */ +uint64_t ngtcp2_strm_rx_offset(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_recv_reordering handles reordered data. + * + * It returns 0 if it succeeds, or one of the following negative error + * codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_strm_recv_reordering(ngtcp2_strm *strm, const uint8_t *data, + size_t datalen, uint64_t offset); + +/* + * ngtcp2_strm_update_rx_offset tells that data up to offset bytes are + * received in order. + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_strm_update_rx_offset(ngtcp2_strm *strm, uint64_t offset); + +/* + * ngtcp2_strm_shutdown shutdowns |strm|. |flags| should be + * NGTCP2_STRM_FLAG_SHUT_RD, and/or NGTCP2_STRM_FLAG_SHUT_WR. + */ +void ngtcp2_strm_shutdown(ngtcp2_strm *strm, uint32_t flags); + +/* + * ngtcp2_strm_streamfrq_push pushes |frc| to streamfrq for + * retransmission. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_strm_streamfrq_push(ngtcp2_strm *strm, ngtcp2_frame_chain *frc); + +/* + * ngtcp2_strm_streamfrq_pop pops the first ngtcp2_frame_chain and + * assigns it to |*pfrc|. This function splits into or merges several + * ngtcp2_frame_chain objects so that the returned ngtcp2_frame_chain + * has at most |left| data length. If there is no frames to send, + * this function returns 0 and |*pfrc| is NULL. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGTCP2_ERR_NOMEM + * Out of memory + */ +int ngtcp2_strm_streamfrq_pop(ngtcp2_strm *strm, ngtcp2_frame_chain **pfrc, + size_t left); + +/* + * ngtcp2_strm_streamfrq_unacked_offset returns the smallest offset of + * unacknowledged stream data held in strm->tx.streamfrq. + */ +uint64_t ngtcp2_strm_streamfrq_unacked_offset(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_streamfrq_top returns the first ngtcp2_frame_chain. + * The queue must not be empty. + */ +ngtcp2_frame_chain *ngtcp2_strm_streamfrq_top(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_streamfrq_empty returns nonzero if streamfrq is empty. + */ +int ngtcp2_strm_streamfrq_empty(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_streamfrq_clear removes all frames from streamfrq. + */ +void ngtcp2_strm_streamfrq_clear(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_is_tx_queued returns nonzero if |strm| is queued. + */ +int ngtcp2_strm_is_tx_queued(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_is_all_tx_data_acked returns nonzero if all outgoing + * data for |strm| which have sent so far have been acknowledged. + */ +int ngtcp2_strm_is_all_tx_data_acked(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_is_all_tx_data_fin_acked behaves like + * ngtcp2_strm_is_all_tx_data_acked, but it also requires that STREAM + * frame with fin bit set is acknowledged. + */ +int ngtcp2_strm_is_all_tx_data_fin_acked(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_get_unacked_range_after returns the range that is not + * acknowledged yet and intersects or comes after |offset|. + */ +ngtcp2_range ngtcp2_strm_get_unacked_range_after(ngtcp2_strm *strm, + uint64_t offset); + +/* + * ngtcp2_strm_get_acked_offset returns offset, that is the data up to + * this offset have been acknowledged by a remote endpoint. It + * returns 0 if no data is acknowledged. + */ +uint64_t ngtcp2_strm_get_acked_offset(ngtcp2_strm *strm); + +/* + * ngtcp2_strm_ack_data tells |strm| that the data [offset, + * offset+len) is acknowledged by a remote endpoint. + */ +int ngtcp2_strm_ack_data(ngtcp2_strm *strm, uint64_t offset, uint64_t len); + +/* + * ngtcp2_strm_set_app_error_code sets |app_error_code| to |strm| and + * set NGTCP2_STRM_FLAG_APP_ERROR_CODE_SET flag. If the flag is + * already set, this function does nothing. + */ +void ngtcp2_strm_set_app_error_code(ngtcp2_strm *strm, uint64_t app_error_code); + +#endif /* NGTCP2_STRM_H */ diff --git a/lib/ngtcp2_unreachable.c b/lib/ngtcp2_unreachable.c new file mode 100644 index 0000000..7c7d9ae --- /dev/null +++ b/lib/ngtcp2_unreachable.c @@ -0,0 +1,71 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_unreachable.h" + +#include <stdio.h> +#include <errno.h> +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ +#include <stdlib.h> +#ifdef WIN32 +# include <io.h> +#endif /* WIN32 */ + +void ngtcp2_unreachable_fail(const char *file, int line, const char *func) { + char *buf; + size_t buflen; + int rv; + +#define NGTCP2_UNREACHABLE_TEMPLATE "%s:%d %s: Unreachable.\n" + + rv = snprintf(NULL, 0, NGTCP2_UNREACHABLE_TEMPLATE, file, line, func); + if (rv < 0) { + abort(); + } + + /* here we explicitly use system malloc */ + buflen = (size_t)rv + 1; + buf = malloc(buflen); + if (buf == NULL) { + abort(); + } + + rv = snprintf(buf, buflen, NGTCP2_UNREACHABLE_TEMPLATE, file, line, func); + if (rv < 0) { + abort(); + } + +#ifndef WIN32 + while (write(STDERR_FILENO, buf, (size_t)rv) == -1 && errno == EINTR) + ; +#else /* WIN32 */ + _write(_fileno(stderr), buf, (unsigned int)rv); +#endif /* WIN32 */ + + free(buf); + + abort(); +} diff --git a/lib/ngtcp2_unreachable.h b/lib/ngtcp2_unreachable.h new file mode 100644 index 0000000..11a6aaa --- /dev/null +++ b/lib/ngtcp2_unreachable.h @@ -0,0 +1,46 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_UNREACHABLE_H +#define NGTCP2_UNREACHABLE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#define ngtcp2_unreachable() \ + ngtcp2_unreachable_fail(__FILE__, __LINE__, __func__) + +#ifdef _MSC_VER +__declspec(noreturn) +#endif /* _MSC_VER */ + void ngtcp2_unreachable_fail(const char *file, int line, const char *func) +#ifndef _MSC_VER + __attribute__((noreturn)) +#endif /* !_MSC_VER */ + ; + +#endif /* NGTCP2_UNREACHABLE_H */ diff --git a/lib/ngtcp2_vec.c b/lib/ngtcp2_vec.c new file mode 100644 index 0000000..257332e --- /dev/null +++ b/lib/ngtcp2_vec.c @@ -0,0 +1,243 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_vec.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_str.h" + +ngtcp2_vec *ngtcp2_vec_init(ngtcp2_vec *vec, const uint8_t *base, size_t len) { + vec->base = (uint8_t *)base; + vec->len = len; + return vec; +} + +int ngtcp2_vec_new(ngtcp2_vec **pvec, const uint8_t *data, size_t datalen, + const ngtcp2_mem *mem) { + size_t len; + uint8_t *p; + + len = sizeof(ngtcp2_vec) + datalen; + + *pvec = ngtcp2_mem_malloc(mem, len); + if (*pvec == NULL) { + return NGTCP2_ERR_NOMEM; + } + + p = (uint8_t *)(*pvec) + sizeof(ngtcp2_vec); + (*pvec)->base = p; + (*pvec)->len = datalen; + if (datalen) { + /* p = */ ngtcp2_cpymem(p, data, datalen); + } + + return 0; +} + +void ngtcp2_vec_del(ngtcp2_vec *vec, const ngtcp2_mem *mem) { + ngtcp2_mem_free(mem, vec); +} + +uint64_t ngtcp2_vec_len(const ngtcp2_vec *vec, size_t n) { + size_t i; + size_t res = 0; + + for (i = 0; i < n; ++i) { + res += vec[i].len; + } + + return res; +} + +int64_t ngtcp2_vec_len_varint(const ngtcp2_vec *vec, size_t n) { + uint64_t res = 0; + size_t len; + size_t i; + + for (i = 0; i < n; ++i) { + len = vec[i].len; + if (len > NGTCP2_MAX_VARINT - res) { + return -1; + } + + res += len; + } + + return (int64_t)res; +} + +ngtcp2_ssize ngtcp2_vec_split(ngtcp2_vec *src, size_t *psrccnt, ngtcp2_vec *dst, + size_t *pdstcnt, size_t left, size_t maxcnt) { + size_t i; + size_t srccnt = *psrccnt; + size_t nmove; + size_t extra = 0; + + for (i = 0; i < srccnt; ++i) { + if (left >= src[i].len) { + left -= src[i].len; + continue; + } + + if (*pdstcnt && src[srccnt - 1].base + src[srccnt - 1].len == dst[0].base) { + if (*pdstcnt + srccnt - i - 1 > maxcnt) { + return -1; + } + + dst[0].len += src[srccnt - 1].len; + dst[0].base = src[srccnt - 1].base; + extra = src[srccnt - 1].len; + --srccnt; + } else if (*pdstcnt + srccnt - i > maxcnt) { + return -1; + } + + if (left == 0) { + *psrccnt = i; + } else { + *psrccnt = i + 1; + } + + nmove = srccnt - i; + if (nmove) { + memmove(dst + nmove, dst, sizeof(ngtcp2_vec) * (*pdstcnt)); + *pdstcnt += nmove; + memcpy(dst, src + i, sizeof(ngtcp2_vec) * nmove); + } + + dst[0].len -= left; + dst[0].base += left; + src[i].len = left; + + if (nmove == 0) { + extra -= left; + } + + return (ngtcp2_ssize)(ngtcp2_vec_len(dst, nmove) + extra); + } + + return 0; +} + +size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src, + size_t *psrccnt, size_t left, size_t maxcnt) { + size_t orig_left = left; + size_t i; + ngtcp2_vec *a, *b; + + assert(maxcnt); + + if (*pdstcnt == 0) { + if (*psrccnt == 0) { + return 0; + } + + a = &dst[0]; + b = &src[0]; + + if (left >= b->len) { + *a = *b; + ++*pdstcnt; + left -= b->len; + i = 1; + } else { + a->len = left; + a->base = b->base; + + b->len -= left; + b->base += left; + + return left; + } + } else { + i = 0; + } + + for (; left && i < *psrccnt; ++i) { + a = &dst[*pdstcnt - 1]; + b = &src[i]; + + if (left >= b->len) { + if (a->base + a->len == b->base) { + a->len += b->len; + } else if (*pdstcnt == maxcnt) { + break; + } else { + dst[(*pdstcnt)++] = *b; + } + left -= b->len; + continue; + } + + if (a->base + a->len == b->base) { + a->len += left; + } else if (*pdstcnt == maxcnt) { + break; + } else { + dst[*pdstcnt].len = left; + dst[*pdstcnt].base = b->base; + ++*pdstcnt; + } + + b->len -= left; + b->base += left; + left = 0; + + break; + } + + memmove(src, src + i, sizeof(ngtcp2_vec) * (*psrccnt - i)); + *psrccnt -= i; + + return orig_left - left; +} + +size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt, + const ngtcp2_vec *src, size_t srccnt, + size_t left) { + size_t i, j; + + for (i = 0, j = 0; left > 0 && i < srccnt && j < dstcnt;) { + if (src[i].len == 0) { + ++i; + continue; + } + dst[j] = src[i]; + if (dst[j].len > left) { + dst[j].len = left; + return j + 1; + } + left -= dst[j].len; + ++i; + ++j; + } + + return j; +} + +void ngtcp2_vec_copy(ngtcp2_vec *dst, const ngtcp2_vec *src, size_t cnt) { + memcpy(dst, src, sizeof(ngtcp2_vec) * cnt); +} diff --git a/lib/ngtcp2_vec.h b/lib/ngtcp2_vec.h new file mode 100644 index 0000000..a39c439 --- /dev/null +++ b/lib/ngtcp2_vec.h @@ -0,0 +1,120 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_VEC_H +#define NGTCP2_VEC_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_mem.h" + +/* + * ngtcp2_vec_lit is a convenient macro to fill the object pointed by + * |DEST| with the literal string |LIT|. + */ +#define ngtcp2_vec_lit(DEST, LIT) \ + ((DEST)->base = (uint8_t *)(LIT), (DEST)->len = sizeof(LIT) - 1, (DEST)) + +/* + * ngtcp2_vec_init initializes |vec| with the given parameters. It + * returns |vec|. + */ +ngtcp2_vec *ngtcp2_vec_init(ngtcp2_vec *vec, const uint8_t *base, size_t len); + +/* + * ngtcp2_vec_new allocates and initializes |*pvec| with given |data| + * of length |datalen|. This function allocates memory for |*pvec| + * and the given data with a single allocation, and the contents + * pointed by |data| is copied into the allocated memory space. To + * free the allocated memory, call ngtcp2_vec_del. + */ +int ngtcp2_vec_new(ngtcp2_vec **pvec, const uint8_t *data, size_t datalen, + const ngtcp2_mem *mem); + +/* + * ngtcp2_vec_del frees the memory allocated by |vec| which is + * allocated and initialized by ngtcp2_vec_new. + */ +void ngtcp2_vec_del(ngtcp2_vec *vec, const ngtcp2_mem *mem); + +/* + * ngtcp2_vec_len returns the sum of length in |vec| of |n| elements. + */ +uint64_t ngtcp2_vec_len(const ngtcp2_vec *vec, size_t n); + +/* + * ngtcp2_vec_len_varint is similar to ngtcp2_vec_len, but it returns + * -1 if the sum of the length exceeds NGTCP2_MAX_VARINT. + */ +int64_t ngtcp2_vec_len_varint(const ngtcp2_vec *vec, size_t n); + +/* + * ngtcp2_vec_split splits |src| to |dst| so that the sum of the + * length in |src| does not exceed |left| bytes. The |maxcnt| is the + * maximum number of elements which |dst| array can contain. The + * caller must set |*psrccnt| to the number of elements of |src|. + * Similarly, the caller must set |*pdstcnt| to the number of elements + * of |dst|. The split does not necessarily occur at the boundary of + * ngtcp2_vec object. After split has done, this function updates + * |*psrccnt| and |*pdstcnt|. This function returns the number of + * bytes moved from |src| to |dst|. If split cannot be made because + * doing so exceeds |maxcnt|, this function returns -1. + */ +ngtcp2_ssize ngtcp2_vec_split(ngtcp2_vec *src, size_t *psrccnt, ngtcp2_vec *dst, + size_t *pdstcnt, size_t left, size_t maxcnt); + +/* + * ngtcp2_vec_merge merges |src| into |dst| by moving at most |left| + * bytes from |src|. The |maxcnt| is the maximum number of elements + * which |dst| array can contain. The caller must set |*pdstcnt| to + * the number of elements of |dst|. Similarly, the caller must set + * |*psrccnt| to the number of elements of |src|. After merge has + * done, this function updates |*psrccnt| and |*pdstcnt|. This + * function returns the number of bytes moved from |src| to |dst|. + */ +size_t ngtcp2_vec_merge(ngtcp2_vec *dst, size_t *pdstcnt, ngtcp2_vec *src, + size_t *psrccnt, size_t left, size_t maxcnt); + +/* + * ngtcp2_vec_copy_at_most copies |src| of length |srccnt| to |dst| of + * length |dstcnt|. The total number of bytes which the copied + * ngtcp2_vec refers to is at most |left|. The empty elements in + * |src| are ignored. This function returns the number of elements + * copied. + */ +size_t ngtcp2_vec_copy_at_most(ngtcp2_vec *dst, size_t dstcnt, + const ngtcp2_vec *src, size_t srccnt, + size_t left); + +/* + * ngtcp2_vec_copy copies |src| of length |cnt| to |dst|. |dst| must + * have sufficient capacity. + */ +void ngtcp2_vec_copy(ngtcp2_vec *dst, const ngtcp2_vec *src, size_t cnt); + +#endif /* NGTCP2_VEC_H */ diff --git a/lib/ngtcp2_version.c b/lib/ngtcp2_version.c new file mode 100644 index 0000000..b31162c --- /dev/null +++ b/lib/ngtcp2_version.c @@ -0,0 +1,39 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +static ngtcp2_info version = {NGTCP2_VERSION_AGE, NGTCP2_VERSION_NUM, + NGTCP2_VERSION}; + +const ngtcp2_info *ngtcp2_version(int least_version) { + if (least_version > NGTCP2_VERSION_NUM) { + return NULL; + } + return &version; +} diff --git a/lib/ngtcp2_window_filter.c b/lib/ngtcp2_window_filter.c new file mode 100644 index 0000000..71c816e --- /dev/null +++ b/lib/ngtcp2_window_filter.c @@ -0,0 +1,99 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Translated to C from the original C++ code + * https://quiche.googlesource.com/quiche/+/5be974e29f7e71a196e726d6e2272676d33ab77d/quic/core/congestion_control/windowed_filter.h + * with the following license: + * + * // Copyright (c) 2016 The Chromium Authors. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ +#include "ngtcp2_window_filter.h" + +#include <string.h> + +void ngtcp2_window_filter_init(ngtcp2_window_filter *wf, + uint64_t window_length) { + wf->window_length = window_length; + memset(wf->estimates, 0, sizeof(wf->estimates)); +} + +void ngtcp2_window_filter_update(ngtcp2_window_filter *wf, uint64_t new_sample, + uint64_t new_time) { + if (wf->estimates[0].sample == 0 || new_sample > wf->estimates[0].sample || + new_time - wf->estimates[2].time > wf->window_length) { + ngtcp2_window_filter_reset(wf, new_sample, new_time); + return; + } + + if (new_sample > wf->estimates[1].sample) { + wf->estimates[1].sample = new_sample; + wf->estimates[1].time = new_time; + wf->estimates[2] = wf->estimates[1]; + } else if (new_sample > wf->estimates[2].sample) { + wf->estimates[2].sample = new_sample; + wf->estimates[2].time = new_time; + } + + if (new_time - wf->estimates[0].time > wf->window_length) { + wf->estimates[0] = wf->estimates[1]; + wf->estimates[1] = wf->estimates[2]; + wf->estimates[2].sample = new_sample; + wf->estimates[2].time = new_time; + + if (new_time - wf->estimates[0].time > wf->window_length) { + wf->estimates[0] = wf->estimates[1]; + wf->estimates[1] = wf->estimates[2]; + } + return; + } + + if (wf->estimates[1].sample == wf->estimates[0].sample && + new_time - wf->estimates[1].time > wf->window_length >> 2) { + wf->estimates[2].sample = new_sample; + wf->estimates[2].time = new_time; + wf->estimates[1] = wf->estimates[2]; + return; + } + + if (wf->estimates[2].sample == wf->estimates[1].sample && + new_time - wf->estimates[2].time > wf->window_length >> 1) { + wf->estimates[2].sample = new_sample; + wf->estimates[2].time = new_time; + } +} + +void ngtcp2_window_filter_reset(ngtcp2_window_filter *wf, uint64_t new_sample, + uint64_t new_time) { + wf->estimates[0].sample = new_sample; + wf->estimates[0].time = new_time; + wf->estimates[1] = wf->estimates[2] = wf->estimates[0]; +} + +uint64_t ngtcp2_window_filter_get_best(ngtcp2_window_filter *wf) { + return wf->estimates[0].sample; +} diff --git a/lib/ngtcp2_window_filter.h b/lib/ngtcp2_window_filter.h new file mode 100644 index 0000000..50415f1 --- /dev/null +++ b/lib/ngtcp2_window_filter.h @@ -0,0 +1,65 @@ +/* + * ngtcp2 + * + * Copyright (c) 2021 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Translated to C from the original C++ code + * https://quiche.googlesource.com/quiche/+/5be974e29f7e71a196e726d6e2272676d33ab77d/quic/core/congestion_control/windowed_filter.h + * with the following license: + * + * // Copyright (c) 2016 The Chromium Authors. All rights reserved. + * // Use of this source code is governed by a BSD-style license that can be + * // found in the LICENSE file. + */ +#ifndef NGTCP2_WINDOW_FILTER_H +#define NGTCP2_WINDOW_FILTER_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +typedef struct ngtcp2_window_filter_sample { + uint64_t sample; + uint64_t time; +} ngtcp2_window_filter_sample; + +typedef struct ngtcp2_window_filter { + uint64_t window_length; + ngtcp2_window_filter_sample estimates[3]; +} ngtcp2_window_filter; + +void ngtcp2_window_filter_init(ngtcp2_window_filter *wf, + uint64_t window_length); + +void ngtcp2_window_filter_update(ngtcp2_window_filter *wf, uint64_t new_sample, + uint64_t new_time); + +void ngtcp2_window_filter_reset(ngtcp2_window_filter *wf, uint64_t new_sample, + uint64_t new_time); + +uint64_t ngtcp2_window_filter_get_best(ngtcp2_window_filter *wf); + +#endif /* NGTCP2_WINDOW_FILTER_H */ 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..a3d964c --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1,1009 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the specified +# version of the C++ standard. If necessary, add switches to CXX and +# CXXCPP to enable support. VERSION may be '11', '14', '17', or '20' for +# the respective C++ standard version. +# +# The second argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for no added switch, and then for an extended mode. +# +# The third argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline support for the specified C++ standard is +# required and that the macro should error out if no mode with that +# support is found. If specified 'optional', then configuration proceeds +# regardless, after defining HAVE_CXX${VERSION} if and only if a +# supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik <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> +# Copyright (c) 2019 Enji Cooper <yaneurabeya@gmail.com> +# Copyright (c) 2020 Jason Merrill <jason@redhat.com> +# Copyright (c) 2021 Jörn Heusipp <osmanx@problemloesungsmaschine.de> +# +# 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 15 + +dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro +dnl (serial version number 13). + +AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl + m4_if([$1], [11], [ax_cxx_compile_alternatives="11 0x"], + [$1], [14], [ax_cxx_compile_alternatives="14 1y"], + [$1], [17], [ax_cxx_compile_alternatives="17 1z"], + [$1], [20], [ax_cxx_compile_alternatives="20"], + [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$2], [], [], + [$2], [ext], [], + [$2], [noext], [], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl + m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true], + [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true], + [$3], [optional], [ax_cxx_compile_cxx$1_required=false], + [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + + m4_if([$2], [], [dnl + AC_CACHE_CHECK(whether $CXX supports C++$1 features by default, + ax_cv_cxx_compile_cxx$1, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [ax_cv_cxx_compile_cxx$1=yes], + [ax_cv_cxx_compile_cxx$1=no])]) + if test x$ax_cv_cxx_compile_cxx$1 = xyes; then + ac_success=yes + fi]) + + m4_if([$2], [noext], [], [dnl + if test x$ac_success = xno; then + for alternative in ${ax_cxx_compile_alternatives}; do + switch="-std=gnu++${alternative}" + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch, + $cachevar, + [ac_save_CXX="$CXX" + CXX="$CXX $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXX="$ac_save_CXX"]) + if eval test x\$$cachevar = xyes; then + CXX="$CXX $switch" + if test -n "$CXXCPP" ; then + CXXCPP="$CXXCPP $switch" + fi + ac_success=yes + break + fi + done + fi]) + + m4_if([$2], [ext], [], [dnl + if test x$ac_success = xno; then + dnl HP's aCC needs +std=c++11 according to: + dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf + dnl Cray's crayCC needs "-h std=c++11" + 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 +) + +dnl Test body for checking C++17 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_17], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 +) + +dnl Test body for checking C++20 support + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_20], + _AX_CXX_COMPILE_STDCXX_testbody_new_in_11 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_14 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_17 + _AX_CXX_COMPILE_STDCXX_testbody_new_in_20 +) + + +dnl Tests for new features in C++11 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[ + +// If the compiler admits that it is not ready for C++11, why torture it? +// Hopefully, this will speed up the test. + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +// MSVC always sets __cplusplus to 199711L in older versions; newer versions +// only set it correctly if /Zc:__cplusplus is specified as well as a +// /std:c++NN switch: +// https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/ +#elif __cplusplus < 201103L && !defined _MSC_VER + +#error "This is not a C++11 compiler" + +#else + +namespace cxx11 +{ + + namespace test_static_assert + { + + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + } + + namespace test_final_override + { + + struct Base + { + virtual ~Base() {} + virtual void f() {} + }; + + struct Derived : public Base + { + virtual ~Derived() override {} + virtual void f() override {} + }; + + } + + namespace test_double_right_angle_brackets + { + + template < typename T > + struct check {}; + + typedef check<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 && !defined _MSC_VER + +#error "This is not a C++14 compiler" + +#else + +namespace cxx14 +{ + + namespace test_polymorphic_lambdas + { + + int + test() + { + const auto lambda = [](auto&&... args){ + const auto istiny = [](auto x){ + return (sizeof(x) == 1UL) ? 1 : 0; + }; + const int aretiny[] = { istiny(args)... }; + return aretiny[0]; + }; + return lambda(1, 1L, 1.0f, '1'); + } + + } + + namespace test_binary_literals + { + + constexpr auto ivii = 0b0000000000101010; + static_assert(ivii == 42, "wrong value"); + + } + + namespace test_generalized_constexpr + { + + template < typename CharT > + constexpr unsigned long + strlen_c(const CharT *const s) noexcept + { + auto length = 0UL; + for (auto p = s; *p; ++p) + ++length; + return length; + } + + static_assert(strlen_c("") == 0UL, ""); + static_assert(strlen_c("x") == 1UL, ""); + static_assert(strlen_c("test") == 4UL, ""); + static_assert(strlen_c("another\0test") == 7UL, ""); + + } + + namespace test_lambda_init_capture + { + + int + test() + { + auto x = 0; + const auto lambda1 = [a = x](int b){ return a + b; }; + const auto lambda2 = [a = lambda1(x)](){ return a; }; + return lambda2(); + } + + } + + namespace test_digit_separators + { + + constexpr auto ten_million = 100'000'000; + static_assert(ten_million == 100000000, ""); + + } + + namespace test_return_type_deduction + { + + auto f(int& x) { return x; } + decltype(auto) g(int& x) { return x; } + + template < typename T1, typename T2 > + struct is_same + { + static constexpr auto value = false; + }; + + template < typename T > + struct is_same<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 && !defined _MSC_VER + +#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 && !defined _MSC_VER + +]]) + + +dnl Tests for new features in C++20 + +m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_20], [[ + +#ifndef __cplusplus + +#error "This is not a C++ compiler" + +#elif __cplusplus < 202002L && !defined _MSC_VER + +#error "This is not a C++20 compiler" + +#else + +#include <version> + +namespace cxx20 +{ + +// As C++20 supports feature test macros in the standard, there is no +// immediate need to actually test for feature availability on the +// Autoconf side. + +} // namespace cxx20 + +#endif // __cplusplus < 202002L && !defined _MSC_VER + +]]) diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..ba2906d --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +main diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..db09b7b --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,68 @@ +# ngtcp2 + +# 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 + ngtcp2_pkt_test.c + ngtcp2_range_test.c + ngtcp2_rob_test.c + ngtcp2_acktr_test.c + ngtcp2_map_test.c + ngtcp2_crypto_test.c + ngtcp2_rtb_test.c + ngtcp2_idtr_test.c + ngtcp2_conn_test.c + ngtcp2_ringbuf_test.c + ngtcp2_conv_test.c + ngtcp2_test_helper.c + ngtcp2_ksl_test.c + ngtcp2_gaptr_test.c + ngtcp2_vec_test.c + ngtcp2_strm_test.c + ngtcp2_pv_test.c + ngtcp2_pmtud_test.c + ngtcp2_str_test.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 + ngtcp2_static + ${CUNIT_LIBRARIES} + ) + 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..3e21c3b --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,94 @@ +# ngtcp2 + +# 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 \ + ngtcp2_pkt_test.c \ + ngtcp2_range_test.c \ + ngtcp2_rob_test.c \ + ngtcp2_acktr_test.c \ + ngtcp2_map_test.c \ + ngtcp2_crypto_test.c \ + ngtcp2_rtb_test.c \ + ngtcp2_idtr_test.c \ + ngtcp2_conn_test.c \ + ngtcp2_ringbuf_test.c \ + ngtcp2_conv_test.c \ + ngtcp2_ksl_test.c \ + ngtcp2_gaptr_test.c \ + ngtcp2_vec_test.c \ + ngtcp2_strm_test.c \ + ngtcp2_pv_test.c \ + ngtcp2_pmtud_test.c \ + ngtcp2_str_test.c \ + ngtcp2_test_helper.c +HFILES= \ + ngtcp2_pkt_test.h \ + ngtcp2_range_test.h \ + ngtcp2_rob_test.h \ + ngtcp2_acktr_test.h \ + ngtcp2_map_test.h \ + ngtcp2_crypto_test.h \ + ngtcp2_rtb_test.h \ + ngtcp2_idtr_test.h \ + ngtcp2_conn_test.h \ + ngtcp2_ringbuf_test.h \ + ngtcp2_conv_test.h \ + ngtcp2_ksl_test.h \ + ngtcp2_gaptr_test.h \ + ngtcp2_vec_test.h \ + ngtcp2_strm_test.h \ + ngtcp2_pv_test.h \ + ngtcp2_pmtud_test.h \ + ngtcp2_str_test.h \ + ngtcp2_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. +if ENABLE_SHARED +main_LDADD = ${top_builddir}/lib/.libs/*.o +else +main_LDADD = ${top_builddir}/lib/.libs/libngtcp2.la +endif +main_LDADD += @CUNIT_LIBS@ +main_LDFLAGS = -static + +AM_CFLAGS = $(WARNCFLAGS) \ + -I${top_srcdir}/lib \ + -I${top_srcdir}/lib/includes \ + -I${top_builddir}/lib/includes \ + -DBUILDING_NGTCP2 \ + @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..d6cd17c --- /dev/null +++ b/tests/main.c @@ -0,0 +1,357 @@ +/* + * ngtcp2 + * + * 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 "ngtcp2_pkt_test.h" +#include "ngtcp2_range_test.h" +#include "ngtcp2_rob_test.h" +#include "ngtcp2_rtb_test.h" +#include "ngtcp2_acktr_test.h" +#include "ngtcp2_crypto_test.h" +#include "ngtcp2_idtr_test.h" +#include "ngtcp2_conn_test.h" +#include "ngtcp2_ringbuf_test.h" +#include "ngtcp2_conv_test.h" +#include "ngtcp2_ksl_test.h" +#include "ngtcp2_map_test.h" +#include "ngtcp2_gaptr_test.h" +#include "ngtcp2_vec_test.h" +#include "ngtcp2_strm_test.h" +#include "ngtcp2_pv_test.h" +#include "ngtcp2_pmtud_test.h" +#include "ngtcp2_str_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("libngtcp2_TestSuite", init_suite1, clean_suite1); + if (NULL == pSuite) { + CU_cleanup_registry(); + return (int)CU_get_error(); + } + + init_static_path(); + + /* add the tests to the suite */ + if (!CU_add_test(pSuite, "pkt_decode_version_cid", + test_ngtcp2_pkt_decode_version_cid) || + !CU_add_test(pSuite, "pkt_decode_hd_long", + test_ngtcp2_pkt_decode_hd_long) || + !CU_add_test(pSuite, "pkt_decode_hd_short", + test_ngtcp2_pkt_decode_hd_short) || + !CU_add_test(pSuite, "pkt_decode_stream_frame", + test_ngtcp2_pkt_decode_stream_frame) || + !CU_add_test(pSuite, "pkt_decode_ack_frame", + test_ngtcp2_pkt_decode_ack_frame) || + !CU_add_test(pSuite, "pkt_decode_padding_frame", + test_ngtcp2_pkt_decode_padding_frame) || + !CU_add_test(pSuite, "pkt_encode_stream_frame", + test_ngtcp2_pkt_encode_stream_frame) || + !CU_add_test(pSuite, "pkt_encode_ack_frame", + test_ngtcp2_pkt_encode_ack_frame) || + !CU_add_test(pSuite, "pkt_encode_ack_ecn_frame", + test_ngtcp2_pkt_encode_ack_ecn_frame) || + !CU_add_test(pSuite, "pkt_encode_reset_stream_frame", + test_ngtcp2_pkt_encode_reset_stream_frame) || + !CU_add_test(pSuite, "pkt_encode_connection_close_frame", + test_ngtcp2_pkt_encode_connection_close_frame) || + !CU_add_test(pSuite, "pkt_encode_connection_close_app_frame", + test_ngtcp2_pkt_encode_connection_close_app_frame) || + !CU_add_test(pSuite, "pkt_encode_max_data_frame", + test_ngtcp2_pkt_encode_max_data_frame) || + !CU_add_test(pSuite, "pkt_encode_max_stream_data_frame", + test_ngtcp2_pkt_encode_max_stream_data_frame) || + !CU_add_test(pSuite, "pkt_encode_max_streams_frame", + test_ngtcp2_pkt_encode_max_streams_frame) || + !CU_add_test(pSuite, "pkt_encode_ping_frame", + test_ngtcp2_pkt_encode_ping_frame) || + !CU_add_test(pSuite, "pkt_encode_data_blocked_frame", + test_ngtcp2_pkt_encode_data_blocked_frame) || + !CU_add_test(pSuite, "pkt_encode_stream_data_blocked_frame", + test_ngtcp2_pkt_encode_stream_data_blocked_frame) || + !CU_add_test(pSuite, "pkt_encode_streams_blocked_frame", + test_ngtcp2_pkt_encode_streams_blocked_frame) || + !CU_add_test(pSuite, "pkt_encode_new_connection_id_frame", + test_ngtcp2_pkt_encode_new_connection_id_frame) || + !CU_add_test(pSuite, "pkt_encode_stop_sending_frame", + test_ngtcp2_pkt_encode_stop_sending_frame) || + !CU_add_test(pSuite, "pkt_encode_path_challenge_frame", + test_ngtcp2_pkt_encode_path_challenge_frame) || + !CU_add_test(pSuite, "pkt_encode_path_response_frame", + test_ngtcp2_pkt_encode_path_response_frame) || + !CU_add_test(pSuite, "pkt_encode_crypto_frame", + test_ngtcp2_pkt_encode_crypto_frame) || + !CU_add_test(pSuite, "pkt_encode_new_token_frame", + test_ngtcp2_pkt_encode_new_token_frame) || + !CU_add_test(pSuite, "pkt_encode_retire_connection_id", + test_ngtcp2_pkt_encode_retire_connection_id_frame) || + !CU_add_test(pSuite, "pkt_encode_handshake_done", + test_ngtcp2_pkt_encode_handshake_done_frame) || + !CU_add_test(pSuite, "pkt_encode_datagram_frame", + test_ngtcp2_pkt_encode_datagram_frame) || + !CU_add_test(pSuite, "pkt_adjust_pkt_num", + test_ngtcp2_pkt_adjust_pkt_num) || + !CU_add_test(pSuite, "pkt_validate_ack", test_ngtcp2_pkt_validate_ack) || + !CU_add_test(pSuite, "pkt_write_stateless_reset", + test_ngtcp2_pkt_write_stateless_reset) || + !CU_add_test(pSuite, "pkt_write_retry", test_ngtcp2_pkt_write_retry) || + !CU_add_test(pSuite, "pkt_write_version_negotiation", + test_ngtcp2_pkt_write_version_negotiation) || + !CU_add_test(pSuite, "pkt_stream_max_datalen", + test_ngtcp2_pkt_stream_max_datalen) || + !CU_add_test(pSuite, "get_varint", test_ngtcp2_get_varint) || + !CU_add_test(pSuite, "get_uvarintlen", test_ngtcp2_get_uvarintlen) || + !CU_add_test(pSuite, "put_uvarintlen", test_ngtcp2_put_uvarintlen) || + !CU_add_test(pSuite, "get_uint64", test_ngtcp2_get_uint64) || + !CU_add_test(pSuite, "get_uint48", test_ngtcp2_get_uint48) || + !CU_add_test(pSuite, "get_uint32", test_ngtcp2_get_uint32) || + !CU_add_test(pSuite, "get_uint24", test_ngtcp2_get_uint24) || + !CU_add_test(pSuite, "get_uint16", test_ngtcp2_get_uint16) || + !CU_add_test(pSuite, "get_uint16be", test_ngtcp2_get_uint16be) || + !CU_add_test(pSuite, "nth_server_bidi_id", + test_ngtcp2_nth_server_bidi_id) || + !CU_add_test(pSuite, "nth_server_uni_id", + test_ngtcp2_nth_server_uni_id) || + !CU_add_test(pSuite, "nth_client_bidi_id", + test_ngtcp2_nth_client_bidi_id) || + !CU_add_test(pSuite, "nth_client_uni_id", + test_ngtcp2_nth_client_uni_id) || + !CU_add_test(pSuite, "range_intersect", test_ngtcp2_range_intersect) || + !CU_add_test(pSuite, "range_cut", test_ngtcp2_range_cut) || + !CU_add_test(pSuite, "range_not_after", test_ngtcp2_range_not_after) || + !CU_add_test(pSuite, "ksl_insert", test_ngtcp2_ksl_insert) || + !CU_add_test(pSuite, "ksl_clear", test_ngtcp2_ksl_clear) || + !CU_add_test(pSuite, "ksl_range", test_ngtcp2_ksl_range) || + !CU_add_test(pSuite, "ksl_update_key_range", + test_ngtcp2_ksl_update_key_range) || + !CU_add_test(pSuite, "ksl_dup", test_ngtcp2_ksl_dup) || + !CU_add_test(pSuite, "ksl_remove_hint", test_ngtcp2_ksl_remove_hint) || + !CU_add_test(pSuite, "rob_push", test_ngtcp2_rob_push) || + !CU_add_test(pSuite, "rob_push_random", test_ngtcp2_rob_push_random) || + !CU_add_test(pSuite, "rob_data_at", test_ngtcp2_rob_data_at) || + !CU_add_test(pSuite, "rob_remove_prefix", + test_ngtcp2_rob_remove_prefix) || + !CU_add_test(pSuite, "acktr_add", test_ngtcp2_acktr_add) || + !CU_add_test(pSuite, "acktr_eviction", test_ngtcp2_acktr_eviction) || + !CU_add_test(pSuite, "acktr_forget", test_ngtcp2_acktr_forget) || + !CU_add_test(pSuite, "acktr_recv_ack", test_ngtcp2_acktr_recv_ack) || + !CU_add_test(pSuite, "encode_transport_params", + test_ngtcp2_encode_transport_params) || + !CU_add_test(pSuite, "decode_transport_params_new", + test_ngtcp2_decode_transport_params_new) || + !CU_add_test(pSuite, "rtb_add", test_ngtcp2_rtb_add) || + !CU_add_test(pSuite, "rtb_recv_ack", test_ngtcp2_rtb_recv_ack) || + !CU_add_test(pSuite, "rtb_lost_pkt_ts", test_ngtcp2_rtb_lost_pkt_ts) || + !CU_add_test(pSuite, "rtb_remove_expired_lost_pkt", + test_ngtcp2_rtb_remove_expired_lost_pkt) || + !CU_add_test(pSuite, "rtb_remove_excessive_lost_pkt", + test_ngtcp2_rtb_remove_excessive_lost_pkt) || + !CU_add_test(pSuite, "idtr_open", test_ngtcp2_idtr_open) || + !CU_add_test(pSuite, "ringbuf_push_front", + test_ngtcp2_ringbuf_push_front) || + !CU_add_test(pSuite, "ringbuf_pop_front", + test_ngtcp2_ringbuf_pop_front) || + !CU_add_test(pSuite, "conn_stream_open_close", + test_ngtcp2_conn_stream_open_close) || + !CU_add_test(pSuite, "conn_stream_rx_flow_control", + test_ngtcp2_conn_stream_rx_flow_control) || + !CU_add_test(pSuite, "conn_stream_rx_flow_control_error", + test_ngtcp2_conn_stream_rx_flow_control_error) || + !CU_add_test(pSuite, "conn_stream_tx_flow_control", + test_ngtcp2_conn_stream_tx_flow_control) || + !CU_add_test(pSuite, "conn_rx_flow_control", + test_ngtcp2_conn_rx_flow_control) || + !CU_add_test(pSuite, "conn_rx_flow_control_error", + test_ngtcp2_conn_rx_flow_control_error) || + !CU_add_test(pSuite, "conn_tx_flow_control", + test_ngtcp2_conn_tx_flow_control) || + !CU_add_test(pSuite, "conn_shutdown_stream_write", + test_ngtcp2_conn_shutdown_stream_write) || + !CU_add_test(pSuite, "conn_recv_reset_stream", + test_ngtcp2_conn_recv_reset_stream) || + !CU_add_test(pSuite, "conn_recv_stop_sending", + test_ngtcp2_conn_recv_stop_sending) || + !CU_add_test(pSuite, "conn_recv_conn_id_omitted", + test_ngtcp2_conn_recv_conn_id_omitted) || + !CU_add_test(pSuite, "conn_short_pkt_type", + test_ngtcp2_conn_short_pkt_type) || + !CU_add_test(pSuite, "conn_recv_stateless_reset", + test_ngtcp2_conn_recv_stateless_reset) || + !CU_add_test(pSuite, "conn_recv_retry", test_ngtcp2_conn_recv_retry) || + !CU_add_test(pSuite, "conn_recv_delayed_handshake_pkt", + test_ngtcp2_conn_recv_delayed_handshake_pkt) || + !CU_add_test(pSuite, "conn_recv_max_streams", + test_ngtcp2_conn_recv_max_streams) || + !CU_add_test(pSuite, "conn_handshake", test_ngtcp2_conn_handshake) || + !CU_add_test(pSuite, "conn_handshake_error", + test_ngtcp2_conn_handshake_error) || + !CU_add_test(pSuite, "conn_retransmit_protected", + test_ngtcp2_conn_retransmit_protected) || + !CU_add_test(pSuite, "conn_send_max_stream_data", + test_ngtcp2_conn_send_max_stream_data) || + !CU_add_test(pSuite, "conn_recv_stream_data", + test_ngtcp2_conn_recv_stream_data) || + !CU_add_test(pSuite, "conn_recv_ping", test_ngtcp2_conn_recv_ping) || + !CU_add_test(pSuite, "conn_recv_max_stream_data", + test_ngtcp2_conn_recv_max_stream_data) || + !CU_add_test(pSuite, "conn_send_early_data", + test_ngtcp2_conn_send_early_data) || + !CU_add_test(pSuite, "conn_recv_early_data", + test_ngtcp2_conn_recv_early_data) || + !CU_add_test(pSuite, "conn_recv_compound_pkt", + test_ngtcp2_conn_recv_compound_pkt) || + !CU_add_test(pSuite, "conn_pkt_payloadlen", + test_ngtcp2_conn_pkt_payloadlen) || + !CU_add_test(pSuite, "conn_writev_stream", + test_ngtcp2_conn_writev_stream) || + !CU_add_test(pSuite, "conn_writev_datagram", + test_ngtcp2_conn_writev_datagram) || + !CU_add_test(pSuite, "conn_recv_datagram", + test_ngtcp2_conn_recv_datagram) || + !CU_add_test(pSuite, "conn_recv_new_connection_id", + test_ngtcp2_conn_recv_new_connection_id) || + !CU_add_test(pSuite, "conn_recv_retire_connection_id", + test_ngtcp2_conn_recv_retire_connection_id) || + !CU_add_test(pSuite, "conn_server_path_validation", + test_ngtcp2_conn_server_path_validation) || + !CU_add_test(pSuite, "conn_client_connection_migration", + test_ngtcp2_conn_client_connection_migration) || + !CU_add_test(pSuite, "conn_recv_path_challenge", + test_ngtcp2_conn_recv_path_challenge) || + !CU_add_test(pSuite, "conn_key_update", test_ngtcp2_conn_key_update) || + !CU_add_test(pSuite, "conn_crypto_buffer_exceeded", + test_ngtcp2_conn_crypto_buffer_exceeded) || + !CU_add_test(pSuite, "conn_handshake_probe", + test_ngtcp2_conn_handshake_probe) || + !CU_add_test(pSuite, "conn_handshake_loss", + test_ngtcp2_conn_handshake_loss) || + !CU_add_test(pSuite, "conn_recv_client_initial_retry", + test_ngtcp2_conn_recv_client_initial_retry) || + !CU_add_test(pSuite, "conn_recv_client_initial_token", + test_ngtcp2_conn_recv_client_initial_token) || + !CU_add_test(pSuite, "conn_get_active_dcid", + test_ngtcp2_conn_get_active_dcid) || + !CU_add_test(pSuite, "conn_recv_version_negotiation", + test_ngtcp2_conn_recv_version_negotiation) || + !CU_add_test(pSuite, "conn_send_initial_token", + test_ngtcp2_conn_send_initial_token) || + !CU_add_test(pSuite, "conn_set_remote_transport_params", + test_ngtcp2_conn_set_remote_transport_params) || + !CU_add_test(pSuite, "conn_write_connection_close", + test_ngtcp2_conn_write_connection_close) || + !CU_add_test(pSuite, "conn_write_application_close", + test_ngtcp2_conn_write_application_close) || + !CU_add_test(pSuite, "conn_rtb_reclaim_on_pto", + test_ngtcp2_conn_rtb_reclaim_on_pto) || + !CU_add_test(pSuite, "conn_rtb_reclaim_on_pto_datagram", + test_ngtcp2_conn_rtb_reclaim_on_pto_datagram) || + !CU_add_test(pSuite, "conn_validate_ecn", + test_ngtcp2_conn_validate_ecn) || + !CU_add_test(pSuite, "conn_path_validation", + test_ngtcp2_conn_path_validation) || + !CU_add_test(pSuite, "conn_early_data_sync_stream_data_limit", + test_ngtcp2_conn_early_data_sync_stream_data_limit) || + !CU_add_test(pSuite, "conn_early_data_rejected", + test_ngtcp2_conn_early_data_rejected) || + !CU_add_test(pSuite, "conn_keep_alive", test_ngtcp2_conn_keep_alive) || + !CU_add_test(pSuite, "conn_retire_stale_bound_dcid", + test_ngtcp2_conn_retire_stale_bound_dcid) || + !CU_add_test(pSuite, "conn_get_scid", test_ngtcp2_conn_get_scid) || + !CU_add_test(pSuite, "conn_stream_close", + test_ngtcp2_conn_stream_close) || + !CU_add_test(pSuite, "conn_buffer_pkt", test_ngtcp2_conn_buffer_pkt) || + !CU_add_test(pSuite, "conn_handshake_timeout", + test_ngtcp2_conn_handshake_timeout) || + !CU_add_test(pSuite, "conn_get_connection_close_error", + test_ngtcp2_conn_get_connection_close_error) || + !CU_add_test(pSuite, "conn_version_negotiation", + test_ngtcp2_conn_version_negotiation) || + !CU_add_test(pSuite, "conn_server_negotiate_version", + test_ngtcp2_conn_server_negotiate_version) || + !CU_add_test(pSuite, "conn_pmtud_loss", test_ngtcp2_conn_pmtud_loss) || + !CU_add_test(pSuite, "conn_amplification", + test_ngtcp2_conn_amplification) || + !CU_add_test(pSuite, "conn_new_failmalloc", + test_ngtcp2_conn_new_failmalloc) || + !CU_add_test(pSuite, "accept", test_ngtcp2_accept) || + !CU_add_test(pSuite, "select_version", test_ngtcp2_select_version) || + !CU_add_test(pSuite, "pkt_write_connection_close", + test_ngtcp2_pkt_write_connection_close) || + !CU_add_test(pSuite, "map", test_ngtcp2_map) || + !CU_add_test(pSuite, "map_functional", test_ngtcp2_map_functional) || + !CU_add_test(pSuite, "map_each_free", test_ngtcp2_map_each_free) || + !CU_add_test(pSuite, "map_clear", test_ngtcp2_map_clear) || + !CU_add_test(pSuite, "gaptr_push", test_ngtcp2_gaptr_push) || + !CU_add_test(pSuite, "gaptr_is_pushed", test_ngtcp2_gaptr_is_pushed) || + !CU_add_test(pSuite, "gaptr_drop_first_gap", + test_ngtcp2_gaptr_drop_first_gap) || + !CU_add_test(pSuite, "vec_split", test_ngtcp2_vec_split) || + !CU_add_test(pSuite, "vec_merge", test_ngtcp2_vec_merge) || + !CU_add_test(pSuite, "vec_len_varint", test_ngtcp2_vec_len_varint) || + !CU_add_test(pSuite, "strm_streamfrq_pop", + test_ngtcp2_strm_streamfrq_pop) || + !CU_add_test(pSuite, "strm_streamfrq_unacked_offset", + test_ngtcp2_strm_streamfrq_unacked_offset) || + !CU_add_test(pSuite, "strm_streamfrq_unacked_pop", + test_ngtcp2_strm_streamfrq_unacked_pop) || + !CU_add_test(pSuite, "pv_add_entry", test_ngtcp2_pv_add_entry) || + !CU_add_test(pSuite, "pv_validate", test_ngtcp2_pv_validate) || + !CU_add_test(pSuite, "pmtud_probe", test_ngtcp2_pmtud_probe) || + !CU_add_test(pSuite, "encode_ipv4", test_ngtcp2_encode_ipv4) || + !CU_add_test(pSuite, "encode_ipv6", test_ngtcp2_encode_ipv6) || + !CU_add_test(pSuite, "get_bytes", test_ngtcp2_get_bytes)) { + 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/ngtcp2_acktr_test.c b/tests/ngtcp2_acktr_test.c new file mode 100644 index 0000000..f75336a --- /dev/null +++ b/tests/ngtcp2_acktr_test.c @@ -0,0 +1,367 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_acktr_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_acktr.h" +#include "ngtcp2_test_helper.h" + +void test_ngtcp2_acktr_add(void) { + const int64_t pkt_nums[] = {1, 5, 7, 6, 2, 3}; + ngtcp2_acktr acktr; + ngtcp2_acktr_entry *ent; + ngtcp2_ksl_it it; + size_t i; + int rv; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_log log; + + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + ngtcp2_acktr_init(&acktr, &log, mem); + + for (i = 0; i < ngtcp2_arraylen(pkt_nums); ++i) { + rv = ngtcp2_acktr_add(&acktr, pkt_nums[i], 1, 999); + + CU_ASSERT(0 == rv); + } + + it = ngtcp2_acktr_get(&acktr); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(7 == ent->pkt_num); + CU_ASSERT(3 == ent->len); + + ngtcp2_ksl_it_next(&it); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(3 == ent->pkt_num); + CU_ASSERT(3 == ent->len); + + ngtcp2_ksl_it_next(&it); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_acktr_free(&acktr); + + /* Check all conditions */ + + /* The lower bound returns the one beyond of the last entry. The + added packet number extends the first entry. */ + ngtcp2_acktr_init(&acktr, &log, mem); + + ngtcp2_acktr_add(&acktr, 1, 1, 100); + ngtcp2_acktr_add(&acktr, 0, 1, 101); + + CU_ASSERT(1 == ngtcp2_ksl_len(&acktr.ents)); + + it = ngtcp2_acktr_get(&acktr); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(1 == ent->pkt_num); + CU_ASSERT(2 == ent->len); + CU_ASSERT(100 == ent->tstamp); + + ngtcp2_acktr_free(&acktr); + + /* The entry is the first one and adding a packet number extends it + to the forward. */ + ngtcp2_acktr_init(&acktr, &log, mem); + + ngtcp2_acktr_add(&acktr, 0, 1, 100); + ngtcp2_acktr_add(&acktr, 1, 1, 101); + + CU_ASSERT(1 == ngtcp2_ksl_len(&acktr.ents)); + + it = ngtcp2_acktr_get(&acktr); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(1 == ent->pkt_num); + CU_ASSERT(2 == ent->len); + CU_ASSERT(101 == ent->tstamp); + + ngtcp2_acktr_free(&acktr); + + /* The adding entry merges the existing 2 entries. */ + ngtcp2_acktr_init(&acktr, &log, mem); + + ngtcp2_acktr_add(&acktr, 0, 1, 100); + ngtcp2_acktr_add(&acktr, 2, 1, 101); + ngtcp2_acktr_add(&acktr, 3, 1, 102); + + CU_ASSERT(2 == ngtcp2_ksl_len(&acktr.ents)); + + ngtcp2_acktr_add(&acktr, 1, 1, 103); + + CU_ASSERT(1 == ngtcp2_ksl_len(&acktr.ents)); + + it = ngtcp2_acktr_get(&acktr); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(3 == ent->pkt_num); + CU_ASSERT(4 == ent->len); + CU_ASSERT(102 == ent->tstamp); + + ngtcp2_acktr_free(&acktr); + + /* Adding entry does not merge the existing 2 entries. It extends + the last entry. */ + ngtcp2_acktr_init(&acktr, &log, mem); + + ngtcp2_acktr_add(&acktr, 0, 1, 100); + ngtcp2_acktr_add(&acktr, 3, 1, 101); + ngtcp2_acktr_add(&acktr, 4, 1, 102); + + CU_ASSERT(2 == ngtcp2_ksl_len(&acktr.ents)); + + ngtcp2_acktr_add(&acktr, 1, 1, 103); + + CU_ASSERT(2 == ngtcp2_ksl_len(&acktr.ents)); + + it = ngtcp2_acktr_get(&acktr); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(4 == ent->pkt_num); + CU_ASSERT(2 == ent->len); + CU_ASSERT(102 == ent->tstamp); + + ngtcp2_ksl_it_next(&it); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(1 == ent->pkt_num); + CU_ASSERT(2 == ent->len); + CU_ASSERT(103 == ent->tstamp); + + ngtcp2_acktr_free(&acktr); + + /* Adding entry does not merge the existing 2 entries. It extends + the first entry. */ + ngtcp2_acktr_init(&acktr, &log, mem); + + ngtcp2_acktr_add(&acktr, 0, 1, 100); + ngtcp2_acktr_add(&acktr, 3, 1, 101); + ngtcp2_acktr_add(&acktr, 4, 1, 102); + + CU_ASSERT(2 == ngtcp2_ksl_len(&acktr.ents)); + + ngtcp2_acktr_add(&acktr, 2, 1, 103); + + CU_ASSERT(2 == ngtcp2_ksl_len(&acktr.ents)); + + it = ngtcp2_acktr_get(&acktr); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(4 == ent->pkt_num); + CU_ASSERT(3 == ent->len); + CU_ASSERT(102 == ent->tstamp); + + ngtcp2_ksl_it_next(&it); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(0 == ent->pkt_num); + CU_ASSERT(1 == ent->len); + CU_ASSERT(100 == ent->tstamp); + + ngtcp2_acktr_free(&acktr); + + /* The added packet number does not extend any entries. */ + ngtcp2_acktr_init(&acktr, &log, mem); + + ngtcp2_acktr_add(&acktr, 0, 1, 0); + ngtcp2_acktr_add(&acktr, 4, 1, 0); + ngtcp2_acktr_add(&acktr, 2, 1, 0); + + CU_ASSERT(3 == ngtcp2_ksl_len(&acktr.ents)); + + ngtcp2_acktr_free(&acktr); +} + +void test_ngtcp2_acktr_eviction(void) { + ngtcp2_acktr acktr; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + size_t i; + ngtcp2_acktr_entry *ent; + const size_t extra = 17; + ngtcp2_log log; + ngtcp2_ksl_it it; + + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + ngtcp2_acktr_init(&acktr, &log, mem); + + for (i = 0; i < NGTCP2_ACKTR_MAX_ENT + extra; ++i) { + ngtcp2_acktr_add(&acktr, (int64_t)(i * 2), 1, 999 + i); + } + + CU_ASSERT(NGTCP2_ACKTR_MAX_ENT == ngtcp2_ksl_len(&acktr.ents)); + + for (i = 0, it = ngtcp2_acktr_get(&acktr); !ngtcp2_ksl_it_end(&it); + ++i, ngtcp2_ksl_it_next(&it)) { + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT((int64_t)((NGTCP2_ACKTR_MAX_ENT + extra - 1) * 2 - i * 2) == + ent->pkt_num); + } + + ngtcp2_acktr_free(&acktr); + + /* Invert insertion order */ + ngtcp2_acktr_init(&acktr, &log, mem); + + for (i = NGTCP2_ACKTR_MAX_ENT + extra; i > 0; --i) { + ngtcp2_acktr_add(&acktr, (int64_t)((i - 1) * 2), 1, 999 + i); + } + + CU_ASSERT(NGTCP2_ACKTR_MAX_ENT == ngtcp2_ksl_len(&acktr.ents)); + + for (i = 0, it = ngtcp2_acktr_get(&acktr); !ngtcp2_ksl_it_end(&it); + ++i, ngtcp2_ksl_it_next(&it)) { + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT((int64_t)((NGTCP2_ACKTR_MAX_ENT + extra - 1) * 2 - i * 2) == + ent->pkt_num); + } + + ngtcp2_acktr_free(&acktr); +} + +void test_ngtcp2_acktr_forget(void) { + ngtcp2_acktr acktr; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + size_t i; + ngtcp2_acktr_entry *ent; + ngtcp2_log log; + ngtcp2_ksl_it it; + + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + ngtcp2_acktr_init(&acktr, &log, mem); + + for (i = 0; i < 7; ++i) { + ngtcp2_acktr_add(&acktr, (int64_t)(i * 2), 1, 999 + i); + } + + CU_ASSERT(7 == ngtcp2_ksl_len(&acktr.ents)); + + it = ngtcp2_acktr_get(&acktr); + ngtcp2_ksl_it_next(&it); + ngtcp2_ksl_it_next(&it); + ngtcp2_ksl_it_next(&it); + ent = ngtcp2_ksl_it_get(&it); + ngtcp2_acktr_forget(&acktr, ent); + + CU_ASSERT(3 == ngtcp2_ksl_len(&acktr.ents)); + + it = ngtcp2_acktr_get(&acktr); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(12 == ent->pkt_num); + + ngtcp2_ksl_it_next(&it); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(10 == ent->pkt_num); + + ngtcp2_ksl_it_next(&it); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(8 == ent->pkt_num); + + it = ngtcp2_acktr_get(&acktr); + ent = ngtcp2_ksl_it_get(&it); + + ngtcp2_acktr_forget(&acktr, ent); + + CU_ASSERT(0 == ngtcp2_ksl_len(&acktr.ents)); + + ngtcp2_acktr_free(&acktr); +} + +void test_ngtcp2_acktr_recv_ack(void) { + ngtcp2_acktr acktr; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + size_t i; + ngtcp2_ack ackfr; + int64_t rpkt_nums[] = { + 4500, 4499, 4497, 4496, 4494, 4493, 4491, 4490, 4488, 4487, 4483, + }; + /* + 4500 4499 + 4497 4496 + 4494 4493 + 4491 4490 + 4488 4487 + 4483 + */ + ngtcp2_acktr_entry *ent; + ngtcp2_log log; + ngtcp2_ksl_it it; + + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + ngtcp2_acktr_init(&acktr, &log, mem); + + for (i = 0; i < ngtcp2_arraylen(rpkt_nums); ++i) { + ngtcp2_acktr_add(&acktr, rpkt_nums[i], 1, 999 + i); + } + + CU_ASSERT(6 == ngtcp2_ksl_len(&acktr.ents)); + + ngtcp2_acktr_add_ack(&acktr, 998, 4497); + ngtcp2_acktr_add_ack(&acktr, 999, 4499); + + ackfr.type = NGTCP2_FRAME_ACK; + ackfr.largest_ack = 998; + ackfr.ack_delay = 0; + ackfr.first_ack_range = 0; + ackfr.rangecnt = 0; + + ngtcp2_acktr_recv_ack(&acktr, &ackfr); + + CU_ASSERT(1 == ngtcp2_ringbuf_len(&acktr.acks)); + CU_ASSERT(1 == ngtcp2_ksl_len(&acktr.ents)); + + it = ngtcp2_ksl_begin(&acktr.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(4500 == ent->pkt_num); + CU_ASSERT(2 == ent->len); + + ackfr.type = NGTCP2_FRAME_ACK; + ackfr.largest_ack = 999; + ackfr.ack_delay = 0; + ackfr.first_ack_range = 0; + ackfr.rangecnt = 0; + + ngtcp2_acktr_recv_ack(&acktr, &ackfr); + + CU_ASSERT(0 == ngtcp2_ringbuf_len(&acktr.acks)); + CU_ASSERT(1 == ngtcp2_ksl_len(&acktr.ents)); + + it = ngtcp2_ksl_begin(&acktr.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(4500 == ent->pkt_num); + CU_ASSERT(1 == ent->len); + + ngtcp2_acktr_free(&acktr); +} diff --git a/tests/ngtcp2_acktr_test.h b/tests/ngtcp2_acktr_test.h new file mode 100644 index 0000000..6e9da5d --- /dev/null +++ b/tests/ngtcp2_acktr_test.h @@ -0,0 +1,37 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_ACKTR_TEST_H +#define NGTCP2_ACKTR_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_acktr_add(void); +void test_ngtcp2_acktr_eviction(void); +void test_ngtcp2_acktr_forget(void); +void test_ngtcp2_acktr_recv_ack(void); + +#endif /* NGTCP2_ACKTR_TEST_H */ diff --git a/tests/ngtcp2_conn_test.c b/tests/ngtcp2_conn_test.c new file mode 100644 index 0000000..f982aea --- /dev/null +++ b/tests/ngtcp2_conn_test.c @@ -0,0 +1,9351 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_conn_test.h" + +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "ngtcp2_conn.h" +#include "ngtcp2_test_helper.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_cid.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_rcvry.h" +#include "ngtcp2_addr.h" +#include "ngtcp2_net.h" + +static void qlog_write(void *user_data, uint32_t flags, const void *data, + size_t datalen) { + (void)user_data; + (void)flags; + (void)data; + (void)datalen; +} + +static int null_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + (void)dest; + (void)aead; + (void)aead_ctx; + (void)plaintext; + (void)plaintextlen; + (void)nonce; + (void)noncelen; + (void)aad; + (void)aadlen; + + if (plaintextlen && plaintext != dest) { + memcpy(dest, plaintext, plaintextlen); + } + memset(dest + plaintextlen, 0, NGTCP2_FAKE_AEAD_OVERHEAD); + + return 0; +} + +static int null_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + (void)dest; + (void)aead; + (void)aead_ctx; + (void)ciphertext; + (void)nonce; + (void)noncelen; + (void)aad; + (void)aadlen; + assert(ciphertextlen >= NGTCP2_FAKE_AEAD_OVERHEAD); + memmove(dest, ciphertext, ciphertextlen - NGTCP2_FAKE_AEAD_OVERHEAD); + return 0; +} + +static int fail_decrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *ciphertext, size_t ciphertextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + (void)dest; + (void)aead; + (void)aead_ctx; + (void)ciphertext; + (void)ciphertextlen; + (void)nonce; + (void)noncelen; + (void)aad; + (void)aadlen; + return NGTCP2_ERR_DECRYPT; +} + +static int null_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + (void)hp; + (void)hp_ctx; + (void)sample; + memcpy(dest, NGTCP2_FAKE_HP_MASK, sizeof(NGTCP2_FAKE_HP_MASK) - 1); + return 0; +} + +static int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, + uint8_t *token, size_t cidlen, + void *user_data) { + (void)user_data; + memset(cid->data, 0, cidlen); + cid->data[0] = (uint8_t)(conn->scid.last_seq + 1); + cid->datalen = cidlen; + memset(token, 0, NGTCP2_STATELESS_RESET_TOKENLEN); + return 0; +} + +static uint8_t null_secret[32]; +static uint8_t null_iv[16]; +static uint8_t null_data[4096]; + +static ngtcp2_crypto_km null_ckm = { + {NULL, 0}, {0}, {null_iv, sizeof(null_iv)}, + -1, 0, NGTCP2_CRYPTO_KM_FLAG_NONE, +}; + +static ngtcp2_path_storage null_path; +static ngtcp2_path_storage new_path; +static ngtcp2_path_storage new_nat_path; + +static ngtcp2_pkt_info null_pi; + +void init_static_path(void) { + path_init(&null_path, 0, 0, 0, 0); + path_init(&new_path, 1, 0, 2, 0); + path_init(&new_nat_path, 0, 0, 0, 1); +} + +static ngtcp2_vec *null_datav(ngtcp2_vec *datav, size_t len) { + datav->base = null_data; + datav->len = len; + return datav; +} + +static void init_crypto_ctx(ngtcp2_crypto_ctx *ctx) { + memset(ctx, 0, sizeof(*ctx)); + ctx->aead.max_overhead = NGTCP2_FAKE_AEAD_OVERHEAD; + ctx->max_encryption = 9999; + ctx->max_decryption_failure = 8888; +} + +static void init_initial_crypto_ctx(ngtcp2_crypto_ctx *ctx) { + memset(ctx, 0, sizeof(*ctx)); + ctx->aead.max_overhead = NGTCP2_INITIAL_AEAD_OVERHEAD; + ctx->max_encryption = 9999; + ctx->max_decryption_failure = 8888; +} + +typedef struct { + uint64_t pkt_num; + /* stream_data is intended to store the arguments passed in + recv_stream_data callback. */ + struct { + int64_t stream_id; + uint32_t flags; + size_t datalen; + } stream_data; + struct { + uint32_t flags; + const uint8_t *data; + size_t datalen; + uint64_t dgram_id; + } datagram; + struct { + uint32_t flags; + int64_t stream_id; + uint64_t app_error_code; + } stream_close; +} my_user_data; + +static int client_initial(ngtcp2_conn *conn, void *user_data) { + (void)user_data; + + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_INITIAL, null_data, + 217); + + return 0; +} + +static int client_initial_early_data(ngtcp2_conn *conn, void *user_data) { + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + + (void)user_data; + + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_INITIAL, null_data, + 217); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_early_crypto_ctx(conn, &crypto_ctx); + ngtcp2_conn_install_early_key(conn, &aead_ctx, null_iv, sizeof(null_iv), + &hp_ctx); + + return 0; +} + +static int client_initial_large_crypto_early_data(ngtcp2_conn *conn, + void *user_data) { + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + + (void)user_data; + + /* Initial CRYPTO data which is larger than a typical single + datagram. */ + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_INITIAL, null_data, + 1500); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_early_crypto_ctx(conn, &crypto_ctx); + ngtcp2_conn_install_early_key(conn, &aead_ctx, null_iv, sizeof(null_iv), + &hp_ctx); + + return 0; +} + +static int recv_client_initial_no_remote_transport_params( + ngtcp2_conn *conn, const ngtcp2_cid *dcid, void *user_data) { + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx ctx; + + (void)dcid; + (void)user_data; + + init_initial_crypto_ctx(&ctx); + + ngtcp2_conn_set_initial_crypto_ctx(conn, &ctx); + ngtcp2_conn_install_initial_key(conn, &aead_ctx, null_iv, &hp_ctx, &aead_ctx, + null_iv, &hp_ctx, sizeof(null_iv)); + + init_crypto_ctx(&ctx); + + ngtcp2_conn_set_crypto_ctx(conn, &ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + return 0; +} + +static int recv_client_initial(ngtcp2_conn *conn, const ngtcp2_cid *dcid, + void *user_data) { + ngtcp2_transport_params params; + + recv_client_initial_no_remote_transport_params(conn, dcid, user_data); + + ngtcp2_transport_params_default(¶ms); + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + return 0; +} + +static int recv_client_initial_early(ngtcp2_conn *conn, const ngtcp2_cid *dcid, + void *user_data) { + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx ctx; + + recv_client_initial(conn, dcid, user_data); + + init_crypto_ctx(&ctx); + + ngtcp2_conn_set_early_crypto_ctx(conn, &ctx); + ngtcp2_conn_install_early_key(conn, &aead_ctx, null_iv, sizeof(null_iv), + &hp_ctx); + + return 0; +} + +static int recv_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, + size_t datalen, void *user_data) { + (void)conn; + (void)crypto_level; + (void)offset; + (void)data; + (void)datalen; + (void)user_data; + return 0; +} + +static int recv_crypto_data_server_early_data(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + uint64_t offset, + const uint8_t *data, + size_t datalen, void *user_data) { + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + + (void)offset; + (void)crypto_level; + (void)data; + (void)datalen; + (void)user_data; + + assert(conn->server); + + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_INITIAL, null_data, + 179); + + ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx, + null_iv, sizeof(null_iv), &hp_ctx); + + conn->callbacks.recv_crypto_data = recv_crypto_data; + + return 0; +} + +static int update_key(ngtcp2_conn *conn, uint8_t *rx_secret, uint8_t *tx_secret, + ngtcp2_crypto_aead_ctx *rx_aead_ctx, uint8_t *rx_iv, + ngtcp2_crypto_aead_ctx *tx_aead_ctx, uint8_t *tx_iv, + const uint8_t *current_rx_secret, + const uint8_t *current_tx_secret, size_t secretlen, + void *user_data) { + (void)conn; + (void)current_rx_secret; + (void)current_tx_secret; + (void)user_data; + (void)secretlen; + + assert(sizeof(null_secret) == secretlen); + + memset(rx_secret, 0xff, sizeof(null_secret)); + memset(tx_secret, 0xff, sizeof(null_secret)); + rx_aead_ctx->native_handle = NULL; + memset(rx_iv, 0xff, sizeof(null_iv)); + tx_aead_ctx->native_handle = NULL; + memset(tx_iv, 0xff, sizeof(null_iv)); + + return 0; +} + +static int recv_crypto_handshake_error(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, + size_t datalen, void *user_data) { + (void)conn; + (void)crypto_level; + (void)offset; + (void)data; + (void)datalen; + (void)user_data; + return NGTCP2_ERR_CRYPTO; +} + +static int recv_crypto_fatal_alert_generated(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + uint64_t offset, + const uint8_t *data, + size_t datalen, void *user_data) { + (void)conn; + (void)crypto_level; + (void)offset; + (void)data; + (void)datalen; + (void)user_data; + return NGTCP2_ERR_CRYPTO; +} + +static int recv_crypto_data_server(ngtcp2_conn *conn, + ngtcp2_crypto_level crypto_level, + uint64_t offset, const uint8_t *data, + size_t datalen, void *user_data) { + (void)offset; + (void)data; + (void)datalen; + (void)user_data; + + ngtcp2_conn_submit_crypto_data(conn, + crypto_level == NGTCP2_CRYPTO_LEVEL_INITIAL + ? NGTCP2_CRYPTO_LEVEL_INITIAL + : NGTCP2_CRYPTO_LEVEL_HANDSHAKE, + null_data, 218); + + return 0; +} + +static int recv_stream_data(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, uint64_t offset, + const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) { + my_user_data *ud = user_data; + (void)conn; + (void)offset; + (void)data; + (void)stream_user_data; + + if (ud) { + ud->stream_data.stream_id = stream_id; + ud->stream_data.flags = flags; + ud->stream_data.datalen = datalen; + } + + return 0; +} + +static int +recv_stream_data_shutdown_stream_read(ngtcp2_conn *conn, uint32_t flags, + int64_t stream_id, uint64_t offset, + const uint8_t *data, size_t datalen, + void *user_data, void *stream_user_data) { + int rv; + + recv_stream_data(conn, flags, stream_id, offset, data, datalen, user_data, + stream_user_data); + + rv = ngtcp2_conn_shutdown_stream_read(conn, stream_id, NGTCP2_APP_ERR01); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int stream_close(ngtcp2_conn *conn, uint32_t flags, int64_t stream_id, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + my_user_data *ud = user_data; + (void)conn; + (void)stream_user_data; + + if (ud) { + ud->stream_close.flags = flags; + ud->stream_close.stream_id = stream_id; + ud->stream_close.app_error_code = app_error_code; + } + + return 0; +} + +static int recv_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, + void *user_data) { + (void)conn; + (void)hd; + (void)user_data; + return 0; +} + +static void genrand(uint8_t *dest, size_t destlen, + const ngtcp2_rand_ctx *rand_ctx) { + (void)rand_ctx; + + memset(dest, 0, destlen); +} + +static int recv_datagram(ngtcp2_conn *conn, uint32_t flags, const uint8_t *data, + size_t datalen, void *user_data) { + my_user_data *ud = user_data; + (void)conn; + (void)flags; + + if (ud) { + ud->datagram.flags = flags; + ud->datagram.data = data; + ud->datagram.datalen = datalen; + } + + return 0; +} + +static int ack_datagram(ngtcp2_conn *conn, uint64_t dgram_id, void *user_data) { + my_user_data *ud = user_data; + (void)conn; + + if (ud) { + ud->datagram.dgram_id = dgram_id; + } + + return 0; +} + +static int get_path_challenge_data(ngtcp2_conn *conn, uint8_t *data, + void *user_data) { + (void)conn; + (void)user_data; + + memset(data, 0, NGTCP2_PATH_CHALLENGE_DATALEN); + + return 0; +} + +static int version_negotiation(ngtcp2_conn *conn, uint32_t version, + const ngtcp2_cid *client_dcid, void *user_data) { + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + (void)client_dcid; + (void)user_data; + + ngtcp2_conn_install_vneg_initial_key(conn, version, &aead_ctx, null_iv, + &hp_ctx, &aead_ctx, null_iv, &hp_ctx, + sizeof(null_iv)); + + return 0; +} + +static void delete_crypto_aead_ctx(ngtcp2_conn *conn, + ngtcp2_crypto_aead_ctx *aead_ctx, + void *user_data) { + (void)conn; + (void)aead_ctx; + (void)user_data; +} + +static void delete_crypto_cipher_ctx(ngtcp2_conn *conn, + ngtcp2_crypto_cipher_ctx *cipher_ctx, + void *user_data) { + (void)conn; + (void)cipher_ctx; + (void)user_data; +} + +static void server_default_settings(ngtcp2_settings *settings) { + memset(settings, 0, sizeof(*settings)); + settings->log_printf = NULL; + settings->initial_ts = 0; + settings->initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; + settings->max_tx_udp_payload_size = 2048; + settings->no_tx_udp_payload_size_shaping = 1; + settings->handshake_timeout = NGTCP2_DEFAULT_HANDSHAKE_TIMEOUT; +} + +static void server_default_transport_params(ngtcp2_transport_params *params) { + size_t i; + + memset(params, 0, sizeof(*params)); + params->initial_max_stream_data_bidi_local = 65535; + params->initial_max_stream_data_bidi_remote = 65535; + params->initial_max_stream_data_uni = 65535; + params->initial_max_data = 128 * 1024; + params->initial_max_streams_bidi = 3; + params->initial_max_streams_uni = 2; + params->max_idle_timeout = 60 * NGTCP2_SECONDS; + params->max_udp_payload_size = 65535; + params->stateless_reset_token_present = 1; + params->active_connection_id_limit = 8; + for (i = 0; i < NGTCP2_STATELESS_RESET_TOKENLEN; ++i) { + params->stateless_reset_token[i] = (uint8_t)i; + } +} + +static void server_default_callbacks(ngtcp2_callbacks *cb) { + memset(cb, 0, sizeof(*cb)); + cb->recv_client_initial = recv_client_initial; + cb->recv_crypto_data = recv_crypto_data_server; + cb->decrypt = null_decrypt; + cb->encrypt = null_encrypt; + cb->hp_mask = null_hp_mask; + cb->rand = genrand; + cb->get_new_connection_id = get_new_connection_id; + cb->update_key = update_key; + cb->delete_crypto_aead_ctx = delete_crypto_aead_ctx; + cb->delete_crypto_cipher_ctx = delete_crypto_cipher_ctx; + cb->get_path_challenge_data = get_path_challenge_data; + cb->version_negotiation = version_negotiation; +} + +static void server_early_callbacks(ngtcp2_callbacks *cb) { + server_default_callbacks(cb); + + cb->recv_client_initial = recv_client_initial_early; + cb->recv_crypto_data = recv_crypto_data_server_early_data; +} + +static void client_default_settings(ngtcp2_settings *settings) { + memset(settings, 0, sizeof(*settings)); + settings->log_printf = NULL; + settings->initial_ts = 0; + settings->initial_rtt = NGTCP2_DEFAULT_INITIAL_RTT; + settings->max_tx_udp_payload_size = 2048; + settings->no_tx_udp_payload_size_shaping = 1; +} + +static void client_default_transport_params(ngtcp2_transport_params *params) { + memset(params, 0, sizeof(*params)); + params->initial_max_stream_data_bidi_local = 65535; + params->initial_max_stream_data_bidi_remote = 65535; + params->initial_max_stream_data_uni = 65535; + params->initial_max_data = 128 * 1024; + params->initial_max_streams_bidi = 0; + params->initial_max_streams_uni = 2; + params->max_idle_timeout = 60 * NGTCP2_SECONDS; + params->max_udp_payload_size = 65535; + params->stateless_reset_token_present = 0; + params->active_connection_id_limit = 8; +} + +static void client_default_callbacks(ngtcp2_callbacks *cb) { + memset(cb, 0, sizeof(*cb)); + cb->client_initial = client_initial; + cb->recv_crypto_data = recv_crypto_data; + cb->decrypt = null_decrypt; + cb->encrypt = null_encrypt; + cb->hp_mask = null_hp_mask; + cb->recv_retry = recv_retry; + cb->rand = genrand; + cb->get_new_connection_id = get_new_connection_id; + cb->update_key = update_key; + cb->delete_crypto_aead_ctx = delete_crypto_aead_ctx; + cb->delete_crypto_cipher_ctx = delete_crypto_cipher_ctx; + cb->get_path_challenge_data = get_path_challenge_data; + cb->version_negotiation = version_negotiation; +} + +static void client_early_callbacks(ngtcp2_callbacks *cb) { + client_default_callbacks(cb); + + cb->client_initial = client_initial_early_data; +} + +static void conn_set_scid_used(ngtcp2_conn *conn) { + ngtcp2_scid *scid; + ngtcp2_ksl_it it; + int rv; + (void)rv; + + assert(1 + (conn->local.transport_params.preferred_address_present != 0) == + ngtcp2_ksl_len(&conn->scid.set)); + + it = ngtcp2_ksl_begin(&conn->scid.set); + scid = ngtcp2_ksl_it_get(&it); + scid->flags |= NGTCP2_SCID_FLAG_USED; + + assert(NGTCP2_PQ_BAD_INDEX == scid->pe.index); + + rv = ngtcp2_pq_push(&conn->scid.used, &scid->pe); + + assert(0 == rv); +} + +static void +setup_default_server_settings(ngtcp2_conn **pconn, const ngtcp2_path *path, + const ngtcp2_settings *settings, + const ngtcp2_transport_params *params) { + ngtcp2_callbacks cb; + ngtcp2_cid dcid, scid; + ngtcp2_transport_params remote_params; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + + dcid_init(&dcid); + scid_init(&scid); + + init_crypto_ctx(&crypto_ctx); + + server_default_callbacks(&cb); + + ngtcp2_conn_server_new(pconn, &dcid, &scid, path, NGTCP2_PROTO_VER_V1, &cb, + settings, params, + /* mem = */ NULL, NULL); + ngtcp2_conn_set_crypto_ctx(*pconn, &crypto_ctx); + ngtcp2_conn_install_rx_handshake_key(*pconn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_handshake_key(*pconn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_rx_key(*pconn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_key(*pconn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + (*pconn)->state = NGTCP2_CS_POST_HANDSHAKE; + (*pconn)->flags |= NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED | + NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED | + NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED | + NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED; + (*pconn)->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + conn_set_scid_used(*pconn); + memset(&remote_params, 0, sizeof(remote_params)); + remote_params.initial_max_stream_data_bidi_local = 64 * 1024; + remote_params.initial_max_stream_data_bidi_remote = 64 * 1024; + remote_params.initial_max_stream_data_uni = 64 * 1024; + remote_params.initial_max_streams_bidi = 0; + remote_params.initial_max_streams_uni = 1; + remote_params.initial_max_data = 64 * 1024; + remote_params.active_connection_id_limit = 8; + remote_params.max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + ngtcp2_transport_params_copy_new(&(*pconn)->remote.transport_params, + &remote_params, (*pconn)->mem); + (*pconn)->local.bidi.max_streams = remote_params.initial_max_streams_bidi; + (*pconn)->local.uni.max_streams = remote_params.initial_max_streams_uni; + (*pconn)->tx.max_offset = remote_params.initial_max_data; + (*pconn)->negotiated_version = (*pconn)->client_chosen_version; +} + +static void setup_default_server(ngtcp2_conn **pconn) { + ngtcp2_settings settings; + ngtcp2_transport_params params; + + server_default_settings(&settings); + server_default_transport_params(¶ms); + + setup_default_server_settings(pconn, &null_path.path, &settings, ¶ms); +} + +static void setup_default_client(ngtcp2_conn **pconn) { + ngtcp2_callbacks cb; + ngtcp2_settings settings; + ngtcp2_transport_params params; + ngtcp2_cid dcid, scid; + ngtcp2_transport_params remote_params; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + + dcid_init(&dcid); + scid_init(&scid); + + init_crypto_ctx(&crypto_ctx); + + client_default_callbacks(&cb); + client_default_settings(&settings); + client_default_transport_params(¶ms); + + ngtcp2_conn_client_new(pconn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + /* mem = */ NULL, NULL); + ngtcp2_conn_set_crypto_ctx(*pconn, &crypto_ctx); + ngtcp2_conn_install_rx_handshake_key(*pconn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_handshake_key(*pconn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_rx_key(*pconn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_key(*pconn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + (*pconn)->state = NGTCP2_CS_POST_HANDSHAKE; + (*pconn)->flags |= NGTCP2_CONN_FLAG_CONN_ID_NEGOTIATED | + NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED | + NGTCP2_CONN_FLAG_HANDSHAKE_COMPLETED_HANDLED | + NGTCP2_CONN_FLAG_HANDSHAKE_CONFIRMED; + (*pconn)->dcid.current.flags |= NGTCP2_DCID_FLAG_PATH_VALIDATED; + conn_set_scid_used(*pconn); + memset(&remote_params, 0, sizeof(remote_params)); + remote_params.initial_max_stream_data_bidi_local = 64 * 1024; + remote_params.initial_max_stream_data_bidi_remote = 64 * 1024; + remote_params.initial_max_stream_data_uni = 64 * 1024; + remote_params.initial_max_streams_bidi = 1; + remote_params.initial_max_streams_uni = 1; + remote_params.initial_max_data = 64 * 1024; + remote_params.active_connection_id_limit = 8; + remote_params.max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + ngtcp2_transport_params_copy_new(&(*pconn)->remote.transport_params, + &remote_params, (*pconn)->mem); + (*pconn)->local.bidi.max_streams = remote_params.initial_max_streams_bidi; + (*pconn)->local.uni.max_streams = remote_params.initial_max_streams_uni; + (*pconn)->tx.max_offset = remote_params.initial_max_data; + (*pconn)->negotiated_version = (*pconn)->client_chosen_version; + + (*pconn)->dcid.current.flags |= NGTCP2_DCID_FLAG_TOKEN_PRESENT; + memset((*pconn)->dcid.current.token, 0xf1, NGTCP2_STATELESS_RESET_TOKENLEN); +} + +static void setup_handshake_server(ngtcp2_conn **pconn) { + ngtcp2_callbacks cb; + ngtcp2_settings settings; + ngtcp2_transport_params params; + ngtcp2_cid dcid, scid; + uint32_t preferred_versions[] = { + NGTCP2_PROTO_VER_V2_DRAFT, + NGTCP2_PROTO_VER_V1, + }; + + dcid_init(&dcid); + scid_init(&scid); + + server_default_callbacks(&cb); + server_default_settings(&settings); + server_default_transport_params(¶ms); + + settings.preferred_versions = preferred_versions; + settings.preferred_versionslen = ngtcp2_arraylen(preferred_versions); + + ngtcp2_conn_server_new(pconn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + /* mem = */ NULL, NULL); +} + +static void setup_handshake_client_version(ngtcp2_conn **pconn, + uint32_t client_chosen_version) { + ngtcp2_callbacks cb; + ngtcp2_settings settings; + ngtcp2_transport_params params; + ngtcp2_cid rcid, scid; + ngtcp2_crypto_aead retry_aead = {0, NGTCP2_FAKE_AEAD_OVERHEAD}; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + uint32_t preferred_versions[] = { + NGTCP2_PROTO_VER_V2_DRAFT, + NGTCP2_PROTO_VER_V1, + }; + uint32_t other_versions[] = { + NGTCP2_PROTO_VER_V1, + NGTCP2_PROTO_VER_V2_DRAFT, + }; + + rcid_init(&rcid); + scid_init(&scid); + + init_initial_crypto_ctx(&crypto_ctx); + + client_default_callbacks(&cb); + client_default_settings(&settings); + client_default_transport_params(¶ms); + + settings.preferred_versions = preferred_versions; + settings.preferred_versionslen = ngtcp2_arraylen(preferred_versions); + + settings.other_versions = other_versions; + settings.other_versionslen = ngtcp2_arraylen(other_versions); + + ngtcp2_conn_client_new(pconn, &rcid, &scid, &null_path.path, + client_chosen_version, &cb, &settings, ¶ms, + /* mem = */ NULL, NULL); + ngtcp2_conn_set_initial_crypto_ctx(*pconn, &crypto_ctx); + ngtcp2_conn_install_initial_key(*pconn, &aead_ctx, null_iv, &hp_ctx, + &aead_ctx, null_iv, &hp_ctx, sizeof(null_iv)); + ngtcp2_conn_set_retry_aead(*pconn, &retry_aead, &aead_ctx); +} + +static void setup_handshake_client(ngtcp2_conn **pconn) { + setup_handshake_client_version(pconn, NGTCP2_PROTO_VER_V1); +} + +static void setup_early_server(ngtcp2_conn **pconn) { + ngtcp2_callbacks cb; + ngtcp2_settings settings; + ngtcp2_transport_params params; + ngtcp2_cid dcid, scid; + + dcid_init(&dcid); + scid_init(&scid); + + server_early_callbacks(&cb); + server_default_settings(&settings); + server_default_transport_params(¶ms); + + ngtcp2_conn_server_new(pconn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + /* mem = */ NULL, NULL); +} + +static void setup_early_client(ngtcp2_conn **pconn) { + ngtcp2_callbacks cb; + ngtcp2_settings settings; + ngtcp2_transport_params params; + ngtcp2_cid rcid, scid; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + + rcid_init(&rcid); + scid_init(&scid); + + init_initial_crypto_ctx(&crypto_ctx); + + client_early_callbacks(&cb); + client_default_settings(&settings); + client_default_transport_params(¶ms); + + ngtcp2_conn_client_new(pconn, &rcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + /* mem = */ NULL, NULL); + ngtcp2_conn_set_initial_crypto_ctx(*pconn, &crypto_ctx); + ngtcp2_conn_install_initial_key(*pconn, &aead_ctx, null_iv, &hp_ctx, + &aead_ctx, null_iv, &hp_ctx, sizeof(null_iv)); + + memset(¶ms, 0, sizeof(params)); + params.initial_max_stream_data_bidi_local = 64 * 1024; + params.initial_max_stream_data_bidi_remote = 64 * 1024; + params.initial_max_stream_data_uni = 64 * 1024; + params.initial_max_streams_bidi = 1; + params.initial_max_streams_uni = 1; + params.initial_max_data = 64 * 1024; + params.active_connection_id_limit = 8; + params.max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + + ngtcp2_conn_set_early_remote_transport_params(*pconn, ¶ms); +} + +void test_ngtcp2_conn_stream_open_close(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + int rv; + ngtcp2_frame fr; + ngtcp2_strm *strm; + int64_t stream_id; + + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 17; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, NULL, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(NGTCP2_STRM_FLAG_NONE == strm->flags); + + fr.stream.fin = 1; + fr.stream.offset = 17; + fr.stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, NULL, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_STRM_FLAG_SHUT_RD == strm->flags); + CU_ASSERT(fr.stream.offset == strm->rx.last_offset); + CU_ASSERT(fr.stream.offset == ngtcp2_strm_rx_offset(strm)); + + spktlen = + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_FIN, 4, NULL, 0, 3); + + CU_ASSERT(spktlen > 0); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(NULL != strm); + + /* Open a remote unidirectional stream */ + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 2; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 19; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 3, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 3); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, 2); + + CU_ASSERT(NGTCP2_STRM_FLAG_SHUT_WR == strm->flags); + CU_ASSERT(fr.stream.data[0].len == strm->rx.last_offset); + CU_ASSERT(fr.stream.data[0].len == ngtcp2_strm_rx_offset(strm)); + + /* Open a local unidirectional stream */ + rv = ngtcp2_conn_open_uni_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + CU_ASSERT(3 == stream_id); + + rv = ngtcp2_conn_open_uni_stream(conn, &stream_id, NULL); + + CU_ASSERT(NGTCP2_ERR_STREAM_ID_BLOCKED == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_stream_rx_flow_control(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + int rv; + ngtcp2_frame fr; + ngtcp2_strm *strm; + size_t i; + int64_t stream_id; + + setup_default_server(&conn); + + conn->local.transport_params.initial_max_stream_data_bidi_remote = 2047; + + for (i = 0; i < 3; ++i) { + stream_id = (int64_t)(i * 4); + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = stream_id; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 1024; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, (int64_t)i, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, stream_id); + + CU_ASSERT(NULL != strm); + + rv = ngtcp2_conn_extend_max_stream_offset(conn, stream_id, + fr.stream.data[0].len); + + CU_ASSERT(0 == rv); + } + + CU_ASSERT(3 == ngtcp2_pq_size(&conn->tx.strmq)); + + strm = ngtcp2_conn_find_stream(conn, 0); + + CU_ASSERT(ngtcp2_strm_is_tx_queued(strm)); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(ngtcp2_strm_is_tx_queued(strm)); + + strm = ngtcp2_conn_find_stream(conn, 8); + + CU_ASSERT(ngtcp2_strm_is_tx_queued(strm)); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 2); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(ngtcp2_pq_empty(&conn->tx.strmq)); + + for (i = 0; i < 3; ++i) { + stream_id = (int64_t)(i * 4); + strm = ngtcp2_conn_find_stream(conn, stream_id); + + CU_ASSERT(2047 + 1024 == strm->rx.max_offset); + } + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_stream_rx_flow_control_error(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + int rv; + ngtcp2_frame fr; + + setup_default_server(&conn); + + conn->local.transport_params.initial_max_stream_data_bidi_remote = 1023; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 1024; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(NGTCP2_ERR_FLOW_CONTROL == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_stream_tx_flow_control(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + int rv; + ngtcp2_frame fr; + ngtcp2_strm *strm; + ngtcp2_ssize nwrite; + int64_t stream_id; + + setup_default_client(&conn); + + conn->remote.transport_params->initial_max_stream_data_bidi_remote = 2047; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, stream_id); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1024 == nwrite); + CU_ASSERT(1024 == strm->tx.offset); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 2); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1023 == nwrite); + CU_ASSERT(2047 == strm->tx.offset); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 3); + + CU_ASSERT(NGTCP2_ERR_STREAM_DATA_BLOCKED == spktlen); + + /* We can write 0 length STREAM frame */ + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 0, 3); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(0 == nwrite); + CU_ASSERT(2047 == strm->tx.offset); + + fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; + fr.max_stream_data.stream_id = stream_id; + fr.max_stream_data.max_stream_data = 2048; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 4); + + CU_ASSERT(0 == rv); + CU_ASSERT(2048 == strm->tx.max_offset); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 5); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1 == nwrite); + CU_ASSERT(2048 == strm->tx.offset); + + ngtcp2_conn_del(conn); + + /* CWND left is round up to the maximum UDP packet size */ + setup_default_client(&conn); + + conn->cstat.cwnd = 1; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_FIN, + stream_id, null_data, 1024, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1024 == nwrite); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_rx_flow_control(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + int rv; + ngtcp2_frame fr; + + setup_default_server(&conn); + + conn->local.transport_params.initial_max_data = 1024; + conn->rx.window = 1024; + conn->rx.max_offset = 1024; + conn->rx.unsent_max_offset = 1024; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 1023; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_extend_max_offset(conn, 1023); + + CU_ASSERT(1024 + 1023 == conn->rx.unsent_max_offset); + CU_ASSERT(1024 == conn->rx.max_offset); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 1023; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 1; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_extend_max_offset(conn, 1); + + CU_ASSERT(2048 == conn->rx.unsent_max_offset); + CU_ASSERT(1024 == conn->rx.max_offset); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 3); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(2048 == conn->rx.max_offset); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_rx_flow_control_error(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + int rv; + ngtcp2_frame fr; + + setup_default_server(&conn); + + conn->local.transport_params.initial_max_data = 1024; + conn->rx.window = 1024; + conn->rx.max_offset = 1024; + conn->rx.unsent_max_offset = 1024; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 1025; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(NGTCP2_ERR_FLOW_CONTROL == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_tx_flow_control(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + int rv; + ngtcp2_frame fr; + ngtcp2_ssize nwrite; + int64_t stream_id; + + setup_default_client(&conn); + + conn->remote.transport_params->initial_max_data = 2048; + conn->tx.max_offset = 2048; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1024 == nwrite); + CU_ASSERT(1024 == conn->tx.offset); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1023, 2); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1023 == nwrite); + CU_ASSERT(1024 + 1023 == conn->tx.offset); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 3); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1 == nwrite); + CU_ASSERT(2048 == conn->tx.offset); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 4); + + CU_ASSERT(spktlen == 0); + CU_ASSERT(-1 == nwrite); + + fr.type = NGTCP2_FRAME_MAX_DATA; + fr.max_data.max_data = 3072; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 5); + + CU_ASSERT(0 == rv); + CU_ASSERT(3072 == conn->tx.max_offset); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 4); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1024 == nwrite); + CU_ASSERT(3072 == conn->tx.offset); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_shutdown_stream_write(void) { + ngtcp2_conn *conn; + int rv; + ngtcp2_frame_chain *frc; + uint8_t buf[2048]; + ngtcp2_frame fr; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_strm *strm; + int64_t stream_id; + + /* Stream not found */ + setup_default_server(&conn); + + rv = ngtcp2_conn_shutdown_stream_write(conn, 4, NGTCP2_APP_ERR01); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); + + /* Check final_size */ + setup_default_client(&conn); + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, null_data, + 1239, 1); + rv = ngtcp2_conn_shutdown_stream_write(conn, stream_id, NGTCP2_APP_ERR01); + + CU_ASSERT(0 == rv); + + for (frc = conn->pktns.tx.frq; frc; frc = frc->next) { + if (frc->fr.type == NGTCP2_FRAME_RESET_STREAM) { + break; + } + } + + CU_ASSERT(NULL != frc); + CU_ASSERT(stream_id == frc->fr.reset_stream.stream_id); + CU_ASSERT(NGTCP2_APP_ERR01 == frc->fr.reset_stream.app_error_code); + CU_ASSERT(1239 == frc->fr.reset_stream.final_size); + + strm = ngtcp2_conn_find_stream(conn, stream_id); + + CU_ASSERT(NULL != strm); + CU_ASSERT(NGTCP2_APP_ERR01 == strm->app_error_code); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 2); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = stream_id; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 100; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 890, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, stream_id)); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 899, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, stream_id)); + + ngtcp2_conn_del(conn); + + /* Check that stream is closed when RESET_STREAM is acknowledged */ + setup_default_client(&conn); + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = stream_id; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 119, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, stream_id)); + + rv = ngtcp2_conn_shutdown_stream_write(conn, stream_id, NGTCP2_APP_ERR01); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, stream_id)); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 2); + + CU_ASSERT(spktlen > 0); + + /* Incoming FIN does not close stream */ + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.fin = 1; + fr.stream.offset = 0; + fr.stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 121, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, stream_id)); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 332, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 3); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, stream_id)); + + ngtcp2_conn_del(conn); + + /* RESET_STREAM is not sent if all tx data are acknowledged */ + setup_default_client(&conn); + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_FIN, stream_id, + null_data, 0, 3); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 999, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 7); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_shutdown_stream_write(conn, stream_id, NGTCP2_APP_ERR01); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, stream_id); + + CU_ASSERT(!(strm->flags & NGTCP2_STRM_FLAG_SENT_RST)); + + spktlen = + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_FIN, -1, NULL, 0, 11); + + CU_ASSERT(0 == spktlen); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_reset_stream(void) { + ngtcp2_conn *conn; + int rv; + uint8_t buf[2048]; + ngtcp2_frame fr; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_strm *strm; + int64_t stream_id; + + /* Receive RESET_STREAM */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, 4, null_data, 354, 2); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 955; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 3); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(strm->flags & NGTCP2_STRM_FLAG_SHUT_RD); + CU_ASSERT(strm->flags & NGTCP2_STRM_FLAG_RECV_RST); + + ngtcp2_conn_del(conn); + + /* Receive RESET_STREAM after sending STOP_SENDING */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, 4, null_data, 354, 2); + ngtcp2_conn_shutdown_stream_read(conn, 4, NGTCP2_APP_ERR01); + ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 3); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 955; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 4); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 4)); + + ngtcp2_conn_del(conn); + + /* Receive RESET_STREAM after sending RESET_STREAM */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, 4, null_data, 354, 2); + ngtcp2_conn_shutdown_stream_write(conn, 4, NGTCP2_APP_ERR01); + ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 3); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 955; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 4); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 4)); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 3, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 5); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, 4)); + + ngtcp2_conn_del(conn); + + /* Receive RESET_STREAM after receiving STOP_SENDING */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, 4, null_data, 354, 2); + + fr.type = NGTCP2_FRAME_STOP_SENDING; + fr.stop_sending.stream_id = 4; + fr.stop_sending.app_error_code = NGTCP2_APP_ERR01; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 3); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 4)); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 4); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 955; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 3, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 4); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 4)); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 4, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 5); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, 4)); + + ngtcp2_conn_del(conn); + + /* final_size in RESET_STREAM exceeds the already received offset */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 954; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(NGTCP2_ERR_FINAL_SIZE == rv); + + ngtcp2_conn_del(conn); + + /* final_size in RESET_STREAM differs from the final offset which + STREAM frame with fin indicated. */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 956; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(NGTCP2_ERR_FINAL_SIZE == rv); + + ngtcp2_conn_del(conn); + + /* RESET_STREAM against local stream which has not been initiated. */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 1; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR01; + fr.reset_stream.final_size = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(NGTCP2_ERR_STREAM_STATE == rv); + + ngtcp2_conn_del(conn); + + /* RESET_STREAM against remote stream which has not been initiated */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 0; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR01; + fr.reset_stream.final_size = 1999; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, 0)); + CU_ASSERT(4 == conn->remote.bidi.unsent_max_streams); + + ngtcp2_conn_del(conn); + + /* RESET_STREAM against remote stream which is larger than allowed + maximum */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 16; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR01; + fr.reset_stream.final_size = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(NGTCP2_ERR_STREAM_LIMIT == rv); + + ngtcp2_conn_del(conn); + + /* RESET_STREAM against remote stream which is allowed, and no + ngtcp2_strm object has been created */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR01; + fr.reset_stream.final_size = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT( + ngtcp2_idtr_is_open(&conn->remote.bidi.idtr, fr.reset_stream.stream_id)); + + ngtcp2_conn_del(conn); + + /* RESET_STREAM against remote stream which is allowed, and no + ngtcp2_strm object has been created, and final_size violates + connection-level flow control. */ + setup_default_server(&conn); + + conn->local.transport_params.initial_max_stream_data_bidi_remote = 1 << 21; + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR01; + fr.reset_stream.final_size = 1 << 20; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(NGTCP2_ERR_FLOW_CONTROL == rv); + + ngtcp2_conn_del(conn); + + /* RESET_STREAM against remote stream which is allowed, and no + ngtcp2_strm object has been created, and final_size violates + stream-level flow control. */ + setup_default_server(&conn); + + conn->rx.max_offset = 1 << 21; + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR01; + fr.reset_stream.final_size = 1 << 20; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(NGTCP2_ERR_FLOW_CONTROL == rv); + + ngtcp2_conn_del(conn); + + /* final_size in RESET_STREAM violates connection-level flow + control */ + setup_default_server(&conn); + + conn->local.transport_params.initial_max_stream_data_bidi_remote = 1 << 21; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 1024 * 1024; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(NGTCP2_ERR_FLOW_CONTROL == rv); + + ngtcp2_conn_del(conn); + + /* final_size in RESET_STREAM violates stream-level flow control */ + setup_default_server(&conn); + + conn->rx.max_offset = 1 << 21; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 1024 * 1024; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(NGTCP2_ERR_FLOW_CONTROL == rv); + + ngtcp2_conn_del(conn); + + /* Receiving RESET_STREAM for a local unidirectional stream is a + protocol violation. */ + setup_default_server(&conn); + + rv = ngtcp2_conn_open_uni_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = stream_id; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(NGTCP2_ERR_PROTO == rv); + + ngtcp2_conn_del(conn); + + /* RESET_STREAM extends connection window including buffered data */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 1; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 1024; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(1024 == conn->rx.offset); + CU_ASSERT(128 * 1024 + 1024 == conn->rx.unsent_max_offset); + + /* Receiving same RESET_STREAM does not increase rx offsets. */ + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 3); + + CU_ASSERT(0 == rv); + CU_ASSERT(1024 == conn->rx.offset); + CU_ASSERT(128 * 1024 + 1024 == conn->rx.unsent_max_offset); + + ngtcp2_conn_del(conn); + + /* Verify that connection window is properly updated when + RESET_STREAM is received after sending STOP_SENDING */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 1; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 955; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, 4, null_data, 354, 2); + ngtcp2_conn_shutdown_stream_read(conn, 4, NGTCP2_APP_ERR01); + ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 3); + + CU_ASSERT(128 * 1024 + 956 == conn->rx.unsent_max_offset); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR02; + fr.reset_stream.final_size = 957; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 4); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 4)); + CU_ASSERT(128 * 1024 + 956 + 1 == conn->rx.unsent_max_offset); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_stop_sending(void) { + ngtcp2_conn *conn; + int rv; + uint8_t buf[2048]; + ngtcp2_frame fr; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_strm *strm; + ngtcp2_tstamp t = 0; + int64_t pkt_num = 0; + ngtcp2_frame_chain *frc; + int64_t stream_id; + + /* Receive STOP_SENDING */ + setup_default_client(&conn); + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, null_data, + 333, ++t); + + fr.type = NGTCP2_FRAME_STOP_SENDING; + fr.stop_sending.stream_id = stream_id; + fr.stop_sending.app_error_code = NGTCP2_APP_ERR01; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, stream_id); + + CU_ASSERT(strm->flags & NGTCP2_STRM_FLAG_SHUT_WR); + CU_ASSERT(strm->flags & NGTCP2_STRM_FLAG_SENT_RST); + + for (frc = conn->pktns.tx.frq; frc; frc = frc->next) { + if (frc->fr.type == NGTCP2_FRAME_RESET_STREAM) { + break; + } + } + + CU_ASSERT(NULL != frc); + CU_ASSERT(NGTCP2_APP_ERR01 == frc->fr.reset_stream.app_error_code); + CU_ASSERT(333 == frc->fr.reset_stream.final_size); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + for (frc = conn->pktns.tx.frq; frc; frc = frc->next) { + if (frc->fr.type == NGTCP2_FRAME_RESET_STREAM) { + break; + } + } + + CU_ASSERT(NULL == frc); + + /* Make sure that receiving duplicated STOP_SENDING does not trigger + another RESET_STREAM. */ + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, stream_id); + + CU_ASSERT(strm->flags & NGTCP2_STRM_FLAG_SHUT_WR); + CU_ASSERT(strm->flags & NGTCP2_STRM_FLAG_SENT_RST); + + for (frc = conn->pktns.tx.frq; frc; frc = frc->next) { + if (frc->fr.type == NGTCP2_FRAME_RESET_STREAM) { + break; + } + } + + CU_ASSERT(NULL == frc); + + ngtcp2_conn_del(conn); + + /* Receive STOP_SENDING after receiving RESET_STREAM */ + setup_default_client(&conn); + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, null_data, + 333, ++t); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = stream_id; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR01; + fr.reset_stream.final_size = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_STOP_SENDING; + fr.stop_sending.stream_id = stream_id; + fr.stop_sending.app_error_code = NGTCP2_APP_ERR01; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, stream_id)); + + for (frc = conn->pktns.tx.frq; frc; frc = frc->next) { + if (frc->fr.type == NGTCP2_FRAME_RESET_STREAM) { + break; + } + } + + CU_ASSERT(NULL != frc); + CU_ASSERT(NGTCP2_APP_ERR01 == frc->fr.reset_stream.app_error_code); + CU_ASSERT(333 == frc->fr.reset_stream.final_size); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, stream_id)); + + ngtcp2_conn_del(conn); + + /* STOP_SENDING against remote bidirectional stream which has not + been initiated. */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STOP_SENDING; + fr.stop_sending.stream_id = 0; + fr.stop_sending.app_error_code = NGTCP2_APP_ERR01; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, 0); + + CU_ASSERT(NULL != strm); + CU_ASSERT(strm->flags & NGTCP2_STRM_FLAG_SHUT_WR); + + ngtcp2_conn_del(conn); + + /* STOP_SENDING against local bidirectional stream which has not + been initiated. */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STOP_SENDING; + fr.stop_sending.stream_id = 1; + fr.stop_sending.app_error_code = NGTCP2_APP_ERR01; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(NGTCP2_ERR_STREAM_STATE == rv); + + ngtcp2_conn_del(conn); + + /* Receiving STOP_SENDING for a local unidirectional stream */ + setup_default_server(&conn); + + rv = ngtcp2_conn_open_uni_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_STOP_SENDING; + fr.stop_sending.stream_id = stream_id; + fr.stop_sending.app_error_code = NGTCP2_APP_ERR01; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_FRAME_RESET_STREAM == conn->pktns.tx.frq->fr.type); + + ngtcp2_conn_del(conn); + + /* STOP_SENDING against local unidirectional stream which has not + been initiated. */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STOP_SENDING; + fr.stop_sending.stream_id = 3; + fr.stop_sending.app_error_code = NGTCP2_APP_ERR01; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(NGTCP2_ERR_STREAM_STATE == rv); + + ngtcp2_conn_del(conn); + + /* STOP_SENDING against local bidirectional stream in Data Sent + state. Because all data have been acknowledged, and FIN is sent, + RESET_STREAM is not necessary. */ + setup_default_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_FIN, stream_id, + null_data, 1, ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_STOP_SENDING; + fr.stop_sending.stream_id = stream_id; + fr.stop_sending.app_error_code = NGTCP2_APP_ERR01; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == conn->pktns.tx.frq); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_conn_id_omitted(void) { + ngtcp2_conn *conn; + int rv; + uint8_t buf[2048]; + ngtcp2_frame fr; + size_t pktlen; + ngtcp2_ksl_it it; + ngtcp2_scid *scid; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 100; + fr.stream.data[0].base = null_data; + + /* Receiving packet which has no connection ID while SCID of server + is not empty. */ + setup_default_server(&conn); + + pktlen = write_pkt(buf, sizeof(buf), /* dcid = */ NULL, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + /* packet is just ignored */ + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, 4)); + + ngtcp2_conn_del(conn); + + /* Allow omission of connection ID */ + setup_default_server(&conn); + ngtcp2_cid_zero(&conn->oscid); + + it = ngtcp2_ksl_begin(&conn->scid.set); + scid = ngtcp2_ksl_it_get(&it); + ngtcp2_cid_zero(&scid->cid); + + pktlen = write_pkt(buf, sizeof(buf), /* dcid = */ NULL, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 4)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_short_pkt_type(void) { + ngtcp2_conn *conn; + ngtcp2_pkt_hd hd; + uint8_t buf[2048]; + ngtcp2_ssize spktlen; + int64_t stream_id; + + /* 1 octet pkt num */ + setup_default_client(&conn); + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(pkt_decode_hd_short_mask(&hd, buf, (size_t)spktlen, + conn->oscid.datalen) > 0); + CU_ASSERT(1 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); + + /* 2 octets pkt num */ + setup_default_client(&conn); + conn->pktns.rtb.largest_acked_tx_pkt_num = 0x6afa2f; + conn->pktns.tx.last_pkt_num = 0x6afd78; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(pkt_decode_hd_short_mask(&hd, buf, (size_t)spktlen, + conn->oscid.datalen) > 0); + CU_ASSERT(2 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); + + /* 4 octets pkt num */ + setup_default_client(&conn); + conn->pktns.rtb.largest_acked_tx_pkt_num = 0x6afa2f; + conn->pktns.tx.last_pkt_num = 0x6bc106; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(pkt_decode_hd_short_mask(&hd, buf, (size_t)spktlen, + conn->oscid.datalen) > 0); + CU_ASSERT(3 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); + + /* 1 octet pkt num (largest)*/ + setup_default_client(&conn); + conn->pktns.rtb.largest_acked_tx_pkt_num = 1; + conn->pktns.tx.last_pkt_num = 128; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(pkt_decode_hd_short_mask(&hd, buf, (size_t)spktlen, + conn->oscid.datalen) > 0); + CU_ASSERT(1 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); + + /* 2 octet pkt num (shortest)*/ + setup_default_client(&conn); + conn->pktns.rtb.largest_acked_tx_pkt_num = 1; + conn->pktns.tx.last_pkt_num = 129; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(pkt_decode_hd_short_mask(&hd, buf, (size_t)spktlen, + conn->oscid.datalen) > 0); + CU_ASSERT(2 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); + + /* 2 octet pkt num (largest)*/ + setup_default_client(&conn); + conn->pktns.rtb.largest_acked_tx_pkt_num = 1; + conn->pktns.tx.last_pkt_num = 32768; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT( + pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->oscid.datalen) > 0); + CU_ASSERT(2 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); + + /* 3 octet pkt num (shortest) */ + setup_default_client(&conn); + conn->pktns.rtb.largest_acked_tx_pkt_num = 1; + conn->pktns.tx.last_pkt_num = 32769; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT( + pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->oscid.datalen) > 0); + CU_ASSERT(3 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); + + /* 3 octet pkt num (largest) */ + setup_default_client(&conn); + conn->pktns.rtb.largest_acked_tx_pkt_num = 1; + conn->pktns.tx.last_pkt_num = 8388608; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT( + pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->oscid.datalen) > 0); + CU_ASSERT(3 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); + + /* 4 octet pkt num (shortest)*/ + setup_default_client(&conn); + conn->pktns.rtb.largest_acked_tx_pkt_num = 1; + conn->pktns.tx.last_pkt_num = 8388609; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT( + pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->oscid.datalen) > 0); + CU_ASSERT(4 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); + + /* Overflow */ + setup_default_client(&conn); + conn->pktns.rtb.largest_acked_tx_pkt_num = 1; + conn->pktns.tx.last_pkt_num = NGTCP2_MAX_PKT_NUM - 1; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 19, 1); + + CU_ASSERT(spktlen > 0); + CU_ASSERT( + pkt_decode_hd_short(&hd, buf, (size_t)spktlen, conn->oscid.datalen) > 0); + CU_ASSERT(4 == hd.pkt_numlen); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_stateless_reset(void) { + ngtcp2_conn *conn; + uint8_t buf[256]; + ngtcp2_ssize spktlen; + int rv; + size_t i; + uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN]; + + for (i = 0; i < NGTCP2_STATELESS_RESET_TOKENLEN; ++i) { + token[i] = (uint8_t)~i; + } + + /* server */ + setup_default_server(&conn); + conn->callbacks.decrypt = fail_decrypt; + conn->pktns.rx.max_pkt_num = 24324325; + + ngtcp2_dcid_set_token(&conn->dcid.current, token); + + spktlen = ngtcp2_pkt_write_stateless_reset( + buf, sizeof(buf), token, null_data, NGTCP2_MIN_STATELESS_RESET_RANDLEN); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, 1); + + CU_ASSERT(NGTCP2_ERR_DRAINING == rv); + CU_ASSERT(NGTCP2_CS_DRAINING == conn->state); + + ngtcp2_conn_del(conn); + + /* client */ + setup_default_client(&conn); + conn->callbacks.decrypt = fail_decrypt; + conn->pktns.rx.max_pkt_num = 3255454; + + ngtcp2_dcid_set_token(&conn->dcid.current, token); + + spktlen = + ngtcp2_pkt_write_stateless_reset(buf, sizeof(buf), token, null_data, 29); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, 1); + + CU_ASSERT(NGTCP2_ERR_DRAINING == rv); + CU_ASSERT(NGTCP2_CS_DRAINING == conn->state); + + ngtcp2_conn_del(conn); + + /* stateless reset in long packet */ + setup_default_server(&conn); + conn->callbacks.decrypt = fail_decrypt; + conn->pktns.rx.max_pkt_num = 754233; + + ngtcp2_dcid_set_token(&conn->dcid.current, token); + + spktlen = ngtcp2_pkt_write_stateless_reset( + buf, sizeof(buf), token, null_data, NGTCP2_MIN_STATELESS_RESET_RANDLEN); + + CU_ASSERT(spktlen > 0); + + /* long packet */ + buf[0] |= NGTCP2_HEADER_FORM_BIT; + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, 1); + + CU_ASSERT(NGTCP2_ERR_DRAINING == rv); + CU_ASSERT(NGTCP2_CS_DRAINING == conn->state); + + ngtcp2_conn_del(conn); + + /* stateless reset in long packet; parsing long header fails */ + setup_default_server(&conn); + conn->callbacks.decrypt = fail_decrypt; + conn->pktns.rx.max_pkt_num = 754233; + + ngtcp2_dcid_set_token(&conn->dcid.current, token); + + spktlen = ngtcp2_pkt_write_stateless_reset( + buf, 41, token, null_data, NGTCP2_MIN_STATELESS_RESET_RANDLEN); + + CU_ASSERT(spktlen > 0); + + /* long packet */ + buf[0] |= NGTCP2_HEADER_FORM_BIT; + buf[0] |= 0x30; + /* Make version nonzero so that it does not look like Version + Negotiation packet */ + buf[1] = 0xff; + /* Make largest CID so that ngtcp2_pkt_decode_hd_long fails */ + buf[5] = 0xff; + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, 1); + + CU_ASSERT(NGTCP2_ERR_DRAINING == rv); + CU_ASSERT(NGTCP2_CS_DRAINING == conn->state); + + ngtcp2_conn_del(conn); + + /* token does not match */ + setup_default_client(&conn); + conn->callbacks.decrypt = fail_decrypt; + conn->pktns.rx.max_pkt_num = 24324325; + + spktlen = + ngtcp2_pkt_write_stateless_reset(buf, sizeof(buf), token, null_data, 29); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_CS_DRAINING != conn->state); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_retry(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_ssize spktlen; + uint64_t t = 0; + ngtcp2_cid dcid; + const uint8_t token[] = "address-validation-token"; + size_t i; + int64_t stream_id; + ngtcp2_ssize datalen; + int rv; + ngtcp2_vec datav; + ngtcp2_strm *strm; + ngtcp2_crypto_aead aead = {0}; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + + dcid_init(&dcid); + setup_handshake_client(&conn); + conn->callbacks.recv_retry = recv_retry; + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + spktlen = ngtcp2_pkt_write_retry( + buf, sizeof(buf), NGTCP2_PROTO_VER_V1, &conn->oscid, &dcid, + ngtcp2_conn_get_dcid(conn), token, strsize(token), null_encrypt, &aead, + &aead_ctx); + + CU_ASSERT(spktlen > 0); + + for (i = 0; i < 2; ++i) { + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, ++t); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + if (i == 1) { + /* Retry packet was ignored */ + CU_ASSERT(spktlen == 0); + } else { + CU_ASSERT(spktlen > 0); + CU_ASSERT(1 == conn->in_pktns->tx.last_pkt_num); + CU_ASSERT(ngtcp2_cid_eq(&dcid, ngtcp2_conn_get_dcid(conn))); + CU_ASSERT(conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY); + } + } + + ngtcp2_conn_del(conn); + + /* Retry packet with non-matching tag is rejected */ + setup_handshake_client(&conn); + conn->callbacks.recv_retry = recv_retry; + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + spktlen = ngtcp2_pkt_write_retry( + buf, sizeof(buf), NGTCP2_PROTO_VER_V1, &conn->oscid, &dcid, + ngtcp2_conn_get_dcid(conn), token, strsize(token), null_encrypt, &aead, + &aead_ctx); + + CU_ASSERT(spktlen > 0); + + /* Change tag */ + buf[spktlen - 1] = 1; + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, ++t); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 == spktlen); + + ngtcp2_conn_del(conn); + + /* Make sure that 0RTT packets are retransmitted */ + setup_early_client(&conn); + conn->callbacks.recv_retry = recv_retry; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = + ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, sizeof(buf), &datalen, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_datav(&datav, 219), 1, ++t); + + CU_ASSERT(sizeof(buf) == spktlen); + CU_ASSERT(219 == datalen); + + spktlen = + ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, sizeof(buf), &datalen, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_datav(&datav, 119), 1, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(119 == datalen); + + spktlen = ngtcp2_pkt_write_retry( + buf, sizeof(buf), NGTCP2_PROTO_VER_V1, &conn->oscid, &dcid, + ngtcp2_conn_get_dcid(conn), token, strsize(token), null_encrypt, &aead, + &aead_ctx); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, ++t); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + /* Make sure that resent 0RTT packet is padded */ + CU_ASSERT(sizeof(buf) == spktlen); + CU_ASSERT(2 == conn->pktns.tx.last_pkt_num); + + strm = ngtcp2_conn_find_stream(conn, stream_id); + + CU_ASSERT(0 == ngtcp2_ksl_len(strm->tx.streamfrq)); + + /* ngtcp2_conn_write_stream sends new 0RTT packet. */ + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &datalen, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 120, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(3 == conn->pktns.tx.last_pkt_num); + CU_ASSERT(120 == datalen); + CU_ASSERT(NULL == conn->pktns.tx.frq); + CU_ASSERT(!ngtcp2_rtb_empty(&conn->pktns.rtb)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_delayed_handshake_pkt(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_frame fr; + int rv; + + setup_default_client(&conn); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 567; + fr.crypto.data[0].base = null_data; + + pktlen = write_handshake_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), 1, + NGTCP2_PROTO_VER_V1, &fr, 1, &null_ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == ngtcp2_ksl_len(&conn->hs_pktns->acktr.ents)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_max_streams(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + int rv; + ngtcp2_frame fr; + + setup_default_client(&conn); + + fr.type = NGTCP2_FRAME_MAX_STREAMS_UNI; + fr.max_streams.max_streams = 999; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(999 == conn->local.uni.max_streams); + + fr.type = NGTCP2_FRAME_MAX_STREAMS_BIDI; + fr.max_streams.max_streams = 997; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 2, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(997 == conn->local.bidi.max_streams); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_handshake(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_frame fr; + int64_t pkt_num = 12345689; + ngtcp2_tstamp t = 0; + ngtcp2_cid rcid; + int rv; + int64_t stream_id; + ngtcp2_ssize nwrite; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + + rcid_init(&rcid); + + /* Make sure server Initial is padded */ + setup_handshake_server(&conn); + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1200; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen >= 1200); + + ngtcp2_conn_del(conn); + + /* Make sure server Handshake is padded when ack-eliciting Initial + is coalesced. */ + setup_handshake_server(&conn); + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1200; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_HANDSHAKE, null_data, + 91); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen >= 1200); + CU_ASSERT(1 == ngtcp2_ksl_len(&conn->hs_pktns->rtb.ents)); + + ngtcp2_conn_del(conn); + + /* Make sure that client packet is padded if it includes Initial and + 0RTT packets */ + setup_early_client(&conn); + + conn->callbacks.client_initial = client_initial_large_crypto_early_data; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + /* First packet should only includes Initial. No space for 0RTT. */ + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, 1280, &nwrite, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 10, ++t); + + CU_ASSERT(1280 == spktlen); + CU_ASSERT(-1 == nwrite); + + /* Second packet has a room for 0RTT. */ + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, 1280, &nwrite, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 10, ++t); + + CU_ASSERT(1280 == spktlen); + CU_ASSERT(10 == nwrite); + + /* We have no data to send. */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, 1280, ++t); + + CU_ASSERT(0 == spktlen); + + ngtcp2_conn_del(conn); + + /* Make sure that client non ack-eliciting Initial triggers + padding. */ + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen >= 1200); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1200; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &conn->oscid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + init_crypto_ctx(&crypto_ctx); + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + pktlen = write_handshake_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen >= 1200); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_handshake_error(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_frame fr; + int64_t pkt_num = 107; + ngtcp2_tstamp t = 0; + ngtcp2_cid rcid; + int rv; + + rcid_init(&rcid); + + /* client side */ + setup_handshake_client(&conn); + conn->callbacks.recv_crypto_data = recv_crypto_handshake_error; + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 333; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &conn->oscid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_CRYPTO == rv); + + ngtcp2_conn_del(conn); + + /* server side */ + setup_handshake_server(&conn); + conn->callbacks.recv_crypto_data = recv_crypto_handshake_error; + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1200; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_CRYPTO == rv); + + ngtcp2_conn_del(conn); + + /* server side; wrong version */ + setup_handshake_server(&conn); + conn->callbacks.recv_crypto_data = recv_crypto_handshake_error; + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1201; + fr.crypto.data[0].base = null_data; + + pktlen = + write_initial_pkt(buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), + ++pkt_num, 0xffff, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_DROP_CONN == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_retransmit_protected(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_ssize spktlen; + ngtcp2_tstamp t = 0; + int64_t stream_id, stream_id_a, stream_id_b; + ngtcp2_ksl_it it; + ngtcp2_frame fr; + size_t pktlen; + ngtcp2_vec datav; + int accepted; + int rv; + ngtcp2_strm *strm; + + /* Retransmit a packet completely */ + setup_default_client(&conn); + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 126, ++t); + + CU_ASSERT(spktlen > 0); + + /* Kick delayed ACK timer */ + t += NGTCP2_SECONDS; + + conn->pktns.tx.last_pkt_num = 1000000009; + conn->pktns.rtb.largest_acked_tx_pkt_num = 1000000007; + it = ngtcp2_rtb_head(&conn->pktns.rtb); + ngtcp2_conn_detect_lost_pkt(conn, &conn->pktns, &conn->cstat, ++t); + + strm = ngtcp2_conn_find_stream(conn, stream_id); + + CU_ASSERT(1 == strm->tx.loss_count); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(NULL == conn->pktns.tx.frq); + + it = ngtcp2_rtb_head(&conn->pktns.rtb); + + CU_ASSERT(!ngtcp2_ksl_it_end(&it)); + + ngtcp2_conn_del(conn); + + /* Retransmission takes place per frame basis. */ + setup_default_client(&conn); + conn->local.bidi.max_streams = 3; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id_a, NULL); + ngtcp2_conn_open_bidi_stream(conn, &stream_id_b, NULL); + + ngtcp2_conn_shutdown_stream_write(conn, stream_id_a, NGTCP2_APP_ERR01); + ngtcp2_conn_shutdown_stream_write(conn, stream_id_b, NGTCP2_APP_ERR01); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + /* Kick delayed ACK timer */ + t += NGTCP2_SECONDS; + + conn->pktns.tx.last_pkt_num = 1000000009; + conn->pktns.rtb.largest_acked_tx_pkt_num = 1000000007; + it = ngtcp2_rtb_head(&conn->pktns.rtb); + ngtcp2_conn_detect_lost_pkt(conn, &conn->pktns, &conn->cstat, ++t); + spktlen = + ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, (size_t)(spktlen - 1), ++t); + + CU_ASSERT(spktlen > 0); + + it = ngtcp2_rtb_head(&conn->pktns.rtb); + + CU_ASSERT(!ngtcp2_ksl_it_end(&it)); + CU_ASSERT(NULL != conn->pktns.tx.frq); + + ngtcp2_conn_del(conn); + + /* DATAGRAM frame must not be retransmitted */ + setup_default_client(&conn); + + conn->callbacks.ack_datagram = ack_datagram; + conn->remote.transport_params->max_datagram_frame_size = 65535; + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + datav.base = null_data; + datav.len = 99; + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_NONE, 1000000009, &datav, 1, ++t); + + CU_ASSERT(spktlen > 0); + + /* Kick delayed ACK timer */ + t += NGTCP2_SECONDS; + + conn->pktns.tx.last_pkt_num = 1000000009; + conn->pktns.rtb.largest_acked_tx_pkt_num = 1000000007; + it = ngtcp2_rtb_head(&conn->pktns.rtb); + ngtcp2_conn_detect_lost_pkt(conn, &conn->pktns, &conn->cstat, ++t); + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 == spktlen); + CU_ASSERT(NULL == conn->pktns.tx.frq); + + it = ngtcp2_rtb_head(&conn->pktns.rtb); + + CU_ASSERT(!ngtcp2_ksl_it_end(&it)); + + ngtcp2_conn_del(conn); + + /* Retransmit an empty STREAM frame */ + setup_default_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + NULL, 0, ++t); + + CU_ASSERT(spktlen > 0); + + /* Kick delayed ACK timer */ + t += NGTCP2_SECONDS; + + conn->pktns.tx.last_pkt_num = 1000000009; + conn->pktns.rtb.largest_acked_tx_pkt_num = 1000000007; + it = ngtcp2_rtb_head(&conn->pktns.rtb); + ngtcp2_conn_detect_lost_pkt(conn, &conn->pktns, &conn->cstat, ++t); + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(NULL == conn->pktns.tx.frq); + + it = ngtcp2_rtb_head(&conn->pktns.rtb); + + CU_ASSERT(!ngtcp2_ksl_it_end(&it)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_send_max_stream_data(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_strm *strm; + int64_t pkt_num = 890; + ngtcp2_tstamp t = 0; + ngtcp2_frame fr; + int rv; + const uint32_t datalen = 1024; + + /* MAX_STREAM_DATA should be sent */ + setup_default_server(&conn); + conn->local.transport_params.initial_max_stream_data_bidi_remote = datalen; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = datalen; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_extend_max_stream_offset(conn, 4, datalen); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(ngtcp2_strm_is_tx_queued(strm)); + + ngtcp2_conn_del(conn); + + /* MAX_STREAM_DATA should not be sent on incoming fin */ + setup_default_server(&conn); + conn->local.transport_params.initial_max_stream_data_bidi_remote = datalen; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = datalen; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_extend_max_stream_offset(conn, 4, datalen); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(!ngtcp2_strm_is_tx_queued(strm)); + + ngtcp2_conn_del(conn); + + /* MAX_STREAM_DATA should not be sent if STOP_SENDING frame is being + sent by local endpoint */ + setup_default_server(&conn); + conn->local.transport_params.initial_max_stream_data_bidi_remote = datalen; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = datalen; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_shutdown_stream_read(conn, 4, NGTCP2_APP_ERR01); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_extend_max_stream_offset(conn, 4, datalen); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(!ngtcp2_strm_is_tx_queued(strm)); + + ngtcp2_conn_del(conn); + + /* MAX_STREAM_DATA should not be sent if stream is being reset by + remote endpoint */ + setup_default_server(&conn); + conn->local.transport_params.initial_max_stream_data_bidi_remote = datalen; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = datalen; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 4; + fr.reset_stream.app_error_code = NGTCP2_APP_ERR01; + fr.reset_stream.final_size = datalen; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_extend_max_stream_offset(conn, 4, datalen); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_pq_empty(&conn->tx.strmq)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_stream_data(void) { + uint8_t buf[1024]; + ngtcp2_conn *conn; + my_user_data ud; + int64_t pkt_num = 612; + ngtcp2_tstamp t = 0; + ngtcp2_frame fr; + size_t pktlen; + int rv; + int64_t stream_id; + size_t i; + + /* 2 STREAM frames are received in the correct order. */ + setup_default_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 111; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(4 == ud.stream_data.stream_id); + CU_ASSERT(!(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN)); + CU_ASSERT(!(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_EARLY)); + CU_ASSERT(111 == ud.stream_data.datalen); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 111; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 99; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(4 == ud.stream_data.stream_id); + CU_ASSERT(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN); + CU_ASSERT(99 == ud.stream_data.datalen); + + ngtcp2_conn_del(conn); + + /* 2 STREAM frames are received in the correct order, and 2nd STREAM + frame has 0 length, and FIN bit set. */ + setup_default_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 111; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(4 == ud.stream_data.stream_id); + CU_ASSERT(!(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN)); + CU_ASSERT(111 == ud.stream_data.datalen); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 111; + fr.stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(4 == ud.stream_data.stream_id); + CU_ASSERT(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN); + CU_ASSERT(0 == ud.stream_data.datalen); + + ngtcp2_conn_del(conn); + + /* 2 identical STREAM frames with FIN bit set are received. The + recv_stream_data callback should not be called for sencond STREAM + frame. */ + setup_default_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 111; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(4 == ud.stream_data.stream_id); + CU_ASSERT(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN); + CU_ASSERT(111 == ud.stream_data.datalen); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.stream_data.stream_id); + CU_ASSERT(!(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN)); + CU_ASSERT(0 == ud.stream_data.datalen); + + ngtcp2_conn_del(conn); + + /* Re-ordered STREAM frame; we first gets 0 length STREAM frame with + FIN bit set. Then the remaining STREAM frame is received. */ + setup_default_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 599; + fr.stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.stream_data.stream_id); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 599; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(4 == ud.stream_data.stream_id); + CU_ASSERT(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN); + CU_ASSERT(599 == ud.stream_data.datalen); + + ngtcp2_conn_del(conn); + + /* Simulate the case where packet is lost. We first gets 0 length + STREAM frame with FIN bit set. Then the lost STREAM frame is + retransmitted with FIN bit set is received. */ + setup_default_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 599; + fr.stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.stream_data.stream_id); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 599; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(4 == ud.stream_data.stream_id); + CU_ASSERT(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN); + CU_ASSERT(599 == ud.stream_data.datalen); + + ngtcp2_conn_del(conn); + + /* Receive an unidirectional stream data */ + setup_default_client(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 3; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 911; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(3 == ud.stream_data.stream_id); + CU_ASSERT(!(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN)); + CU_ASSERT(911 == ud.stream_data.datalen); + + ngtcp2_conn_del(conn); + + /* Receive an unidirectional stream which is beyond the limit. */ + setup_default_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->remote.uni.max_streams = 0; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 2; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 911; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_STREAM_LIMIT == rv); + + ngtcp2_conn_del(conn); + + /* Receiving nonzero payload for an local unidirectional stream is a + protocol violation. */ + setup_default_client(&conn); + + rv = ngtcp2_conn_open_uni_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = stream_id; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 9; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_STREAM_STATE == rv); + + ngtcp2_conn_del(conn); + + /* DATA on crypto stream, and TLS alert is generated. */ + setup_default_server(&conn); + conn->callbacks.recv_crypto_data = recv_crypto_fatal_alert_generated; + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 139; + fr.crypto.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_CRYPTO == rv); + + ngtcp2_conn_del(conn); + + /* 0 length STREAM frame is allowed */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 4)); + + ngtcp2_conn_del(conn); + + /* After sending STOP_SENDING, receiving 2 STREAM frames with fin + bit set must not invoke recv_stream_data callback. */ + setup_default_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 4)); + + rv = ngtcp2_conn_shutdown_stream_read(conn, 4, 99); + + CU_ASSERT(0 == rv); + + for (i = 0; i < 2; ++i) { + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].base = null_data; + fr.stream.data[0].len = 19; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + ud.stream_data.stream_id = 0; + rv = + ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.stream_data.stream_id); + CU_ASSERT(19 == conn->rx.offset); + CU_ASSERT(19 == conn->rx.unsent_max_offset - + conn->local.transport_params.initial_max_data); + CU_ASSERT(conn->local.transport_params.initial_max_data == + conn->rx.max_offset); + } + + ngtcp2_conn_del(conn); + + /* After receiving RESET_STREAM, recv_stream_data callback must not + be invoked */ + setup_default_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 0; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 0)); + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.reset_stream.stream_id = 0; + fr.reset_stream.app_error_code = 999; + fr.reset_stream.final_size = 199; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != ngtcp2_conn_find_stream(conn, 0)); + CU_ASSERT(199 == conn->rx.unsent_max_offset - + conn->local.transport_params.initial_max_data); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 0; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].base = null_data; + fr.stream.data[0].len = 198; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + ud.stream_data.stream_id = -1; + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(-1 == ud.stream_data.stream_id); + CU_ASSERT(199 == conn->rx.unsent_max_offset - + conn->local.transport_params.initial_max_data); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 0; + fr.stream.fin = 1; + fr.stream.offset = 198; + fr.stream.datacnt = 1; + fr.stream.data[0].base = null_data; + fr.stream.data[0].len = 1; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + ud.stream_data.stream_id = -1; + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(-1 == ud.stream_data.stream_id); + CU_ASSERT(199 == conn->rx.unsent_max_offset - + conn->local.transport_params.initial_max_data); + + ngtcp2_conn_del(conn); + + /* ngtcp2_conn_shutdown_stream_read is called in recv_stream_data + callback. Further recv_stream_data callback must not be + called. */ + setup_default_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data_shutdown_stream_read; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 599; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 1; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.stream_data.stream_id); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 599; + fr.stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(4 == ud.stream_data.stream_id); + CU_ASSERT(!(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN)); + CU_ASSERT(599 == ud.stream_data.datalen); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_ping(void) { + uint8_t buf[1024]; + ngtcp2_conn *conn; + int64_t pkt_num = 133; + ngtcp2_tstamp t = 0; + ngtcp2_frame fr; + size_t pktlen; + int rv; + + setup_default_client(&conn); + + fr.type = NGTCP2_FRAME_PING; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == conn->pktns.tx.frq); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_max_stream_data(void) { + uint8_t buf[1024]; + ngtcp2_conn *conn; + int64_t pkt_num = 1000000007; + ngtcp2_tstamp t = 0; + ngtcp2_frame fr; + size_t pktlen; + int rv; + ngtcp2_strm *strm; + + /* Receiving MAX_STREAM_DATA to an uninitiated local bidirectional + stream ID is an error */ + setup_default_client(&conn); + + fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; + fr.max_stream_data.stream_id = 4; + fr.max_stream_data.max_stream_data = 8092; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_STREAM_STATE == rv); + + ngtcp2_conn_del(conn); + + /* Receiving MAX_STREAM_DATA to an uninitiated local unidirectional + stream ID is an error */ + setup_default_client(&conn); + + fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; + fr.max_stream_data.stream_id = 2; + fr.max_stream_data.max_stream_data = 8092; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_STREAM_STATE == rv); + + ngtcp2_conn_del(conn); + + /* Receiving MAX_STREAM_DATA to a remote bidirectional stream which + exceeds limit */ + setup_default_client(&conn); + + fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; + fr.max_stream_data.stream_id = 1; + fr.max_stream_data.max_stream_data = 1000000009; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_STREAM_LIMIT == rv); + + ngtcp2_conn_del(conn); + + /* Receiving MAX_STREAM_DATA to a remote bidirectional stream which + the local endpoint has not received yet. */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; + fr.max_stream_data.stream_id = 4; + fr.max_stream_data.max_stream_data = 1000000009; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(NULL != strm); + CU_ASSERT(1000000009 == strm->tx.max_offset); + + ngtcp2_conn_del(conn); + + /* Receiving MAX_STREAM_DATA to a idle remote unidirectional stream + is a protocol violation. */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; + fr.max_stream_data.stream_id = 2; + fr.max_stream_data.max_stream_data = 1000000009; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_STREAM_STATE == rv); + + ngtcp2_conn_del(conn); + + /* Receiving MAX_STREAM_DATA to an existing bidirectional stream */ + setup_default_server(&conn); + + strm = open_stream(conn, 4); + + fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; + fr.max_stream_data.stream_id = 4; + fr.max_stream_data.max_stream_data = 1000000009; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1000000009 == strm->tx.max_offset); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_send_early_data(void) { + ngtcp2_conn *conn; + ngtcp2_ssize spktlen; + ngtcp2_ssize datalen; + uint8_t buf[1024]; + int64_t stream_id; + int rv; + ngtcp2_tstamp t = 0; + ngtcp2_vec datav; + + setup_early_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &datalen, NGTCP2_WRITE_STREAM_FLAG_FIN, + stream_id, null_data, 1024, ++t); + + CU_ASSERT((ngtcp2_ssize)sizeof(buf) == spktlen); + CU_ASSERT(670 == datalen); + + ngtcp2_conn_del(conn); + + /* Verify that Handshake packet and 0-RTT packet are coalesced into + one UDP packet. */ + setup_early_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = + ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, sizeof(buf), &datalen, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_datav(&datav, 199), 1, ++t); + + CU_ASSERT(sizeof(buf) == spktlen); + CU_ASSERT(199 == datalen); + + ngtcp2_conn_del(conn); + + /* 0 length 0-RTT packet with FIN bit set */ + setup_early_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, sizeof(buf), + &datalen, NGTCP2_WRITE_STREAM_FLAG_FIN, + stream_id, NULL, 0, ++t); + + CU_ASSERT(sizeof(buf) == spktlen); + CU_ASSERT(0 == datalen); + + ngtcp2_conn_del(conn); + + /* Can write 0 length STREAM frame */ + setup_early_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, sizeof(buf), + &datalen, NGTCP2_WRITE_STREAM_FLAG_NONE, + -1, NULL, 0, ++t); + + CU_ASSERT(spktlen > 0); + + /* We have written Initial. Now check that STREAM frame is + written. */ + spktlen = ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, sizeof(buf), + &datalen, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, NULL, 0, ++t); + + CU_ASSERT(spktlen > 0); + + ngtcp2_conn_del(conn); + + /* Could not send 0-RTT data because buffer is too small. */ + setup_early_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_writev_stream( + conn, NULL, NULL, buf, + NGTCP2_MIN_LONG_HEADERLEN + 1 + ngtcp2_conn_get_dcid(conn)->datalen + + conn->oscid.datalen + 300, + &datalen, NGTCP2_WRITE_STREAM_FLAG_FIN, stream_id, NULL, 0, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(-1 == datalen); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_early_data(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_frame fr; + int64_t pkt_num = 1; + ngtcp2_tstamp t = 0; + ngtcp2_strm *strm; + ngtcp2_cid rcid; + int rv; + my_user_data ud; + + rcid_init(&rcid); + + setup_early_server(&conn); + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1221; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 911; + fr.stream.data[0].base = null_data; + + pktlen = + write_0rtt_pkt(buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), + ++pkt_num, conn->client_chosen_version, &fr, 1, &null_ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(4 == ud.stream_data.stream_id); + CU_ASSERT(ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_EARLY); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(NULL != strm); + CU_ASSERT(911 == strm->rx.last_offset); + + ngtcp2_conn_del(conn); + + /* Re-ordered 0-RTT packet */ + setup_early_server(&conn); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 119; + fr.stream.data[0].base = null_data; + + pktlen = + write_0rtt_pkt(buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), + ++pkt_num, conn->client_chosen_version, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_RETRY == rv); + + ngtcp2_conn_del(conn); + + /* Compound packet */ + setup_early_server(&conn); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 111; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 1; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 999; + fr.stream.data[0].base = null_data; + + pktlen += write_0rtt_pkt(buf + pktlen, sizeof(buf) - pktlen, &rcid, + ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + strm = ngtcp2_conn_find_stream(conn, 4); + + CU_ASSERT(NULL != strm); + CU_ASSERT(999 == strm->rx.last_offset); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_compound_pkt(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_frame fr; + int64_t pkt_num = 1; + ngtcp2_tstamp t = 0; + ngtcp2_acktr_entry *ackent; + int rv; + ngtcp2_ksl_it it; + + /* 2 QUIC long packets in one UDP packet */ + setup_handshake_server(&conn); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 611; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &conn->oscid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + pktlen += write_initial_pkt(buf + pktlen, sizeof(buf) - pktlen, &conn->oscid, + ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, + &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + it = ngtcp2_acktr_get(&conn->in_pktns->acktr); + ackent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(pkt_num == ackent->pkt_num); + CU_ASSERT(2 == ackent->len); + + ngtcp2_ksl_it_next(&it); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_conn_del(conn); + + /* 1 long packet and 1 short packet in one UDP packet */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_PADDING; + fr.padding.len = 1; + + pktlen = write_handshake_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, &fr, 1, &null_ckm); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 426; + fr.stream.data[0].base = null_data; + + pktlen += write_pkt(buf + pktlen, sizeof(buf) - pktlen, &conn->oscid, + ++pkt_num, &fr, 1, conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + it = ngtcp2_acktr_get(&conn->pktns.acktr); + ackent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(ackent->pkt_num == pkt_num); + + it = ngtcp2_acktr_get(&conn->hs_pktns->acktr); + + CU_ASSERT(!ngtcp2_ksl_it_end(&it)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_pkt_payloadlen(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_frame fr; + int64_t pkt_num = 1; + ngtcp2_tstamp t = 0; + uint64_t payloadlen; + int rv; + const ngtcp2_cid *dcid; + + /* Payload length is invalid */ + setup_handshake_server(&conn); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1231; + fr.crypto.data[0].base = null_data; + + dcid = ngtcp2_conn_get_dcid(conn); + + pktlen = write_initial_pkt(buf, sizeof(buf), &conn->oscid, dcid, ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, + &null_ckm); + + payloadlen = read_pkt_payloadlen(buf, dcid, &conn->oscid); + write_pkt_payloadlen(buf, dcid, &conn->oscid, payloadlen + 1); + + /* This first packet which does not increase initial packet number + space CRYPTO offset or it does not get buffered as 0RTT is an + error. But it is unsecured Initial, so we just ignore it. */ + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_DROP_CONN == rv); + CU_ASSERT(NGTCP2_CS_SERVER_INITIAL == conn->state); + + ngtcp2_conn_del(conn); + + /* Client Initial packet included in UDP datagram smaller than 1200 + is discarded. */ + setup_handshake_server(&conn); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1000; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), 0, NGTCP2_PROTO_VER_V1, + NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_DROP_CONN == rv); + CU_ASSERT(NGTCP2_CS_SERVER_INITIAL == conn->state); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_writev_stream(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_ssize spktlen; + ngtcp2_tstamp t = 0; + int rv; + int64_t stream_id; + ngtcp2_vec datav = {null_data, 10}; + ngtcp2_ssize datalen; + size_t left; + + /* 0 length STREAM should not be written if we supply nonzero length + data. */ + setup_default_client(&conn); + + /* This will sends NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + /* + * Long header (1+18+1) + * STREAM overhead (+3) + * AEAD overhead (16) + */ + spktlen = ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, 39, &datalen, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + &datav, 1, ++t); + + CU_ASSERT(0 == spktlen); + CU_ASSERT(-1 == datalen); + + ngtcp2_conn_del(conn); + + /* +1 buffer size */ + setup_default_client(&conn); + + /* This will sends NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, 40, &datalen, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + &datav, 1, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1 == datalen); + + ngtcp2_conn_del(conn); + + /* Coalesces multiple STREAM frames */ + setup_default_client(&conn); + conn->local.bidi.max_streams = 100; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, 1200, &datalen, + NGTCP2_WRITE_STREAM_FLAG_MORE, stream_id, + &datav, 1, ++t); + + CU_ASSERT(NGTCP2_ERR_WRITE_MORE == spktlen); + CU_ASSERT(10 == datalen); + + left = ngtcp2_ppe_left(&conn->pkt.ppe); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, 1200, &datalen, + NGTCP2_WRITE_STREAM_FLAG_MORE, stream_id, + &datav, 1, ++t); + + CU_ASSERT(NGTCP2_ERR_WRITE_MORE == spktlen); + CU_ASSERT(10 == datalen); + CU_ASSERT(ngtcp2_ppe_left(&conn->pkt.ppe) < left); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + ngtcp2_conn_del(conn); + + /* 0RTT: Coalesces multiple STREAM frames */ + setup_early_client(&conn); + conn->local.bidi.max_streams = 100; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, 1200, &datalen, + NGTCP2_WRITE_STREAM_FLAG_MORE, stream_id, + &datav, 1, ++t); + + CU_ASSERT(NGTCP2_ERR_WRITE_MORE == spktlen); + CU_ASSERT(10 == datalen); + + left = ngtcp2_ppe_left(&conn->pkt.ppe); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_writev_stream(conn, NULL, NULL, buf, 1200, &datalen, + NGTCP2_WRITE_STREAM_FLAG_MORE, stream_id, + &datav, 1, ++t); + + CU_ASSERT(NGTCP2_ERR_WRITE_MORE == spktlen); + CU_ASSERT(10 == datalen); + CU_ASSERT(ngtcp2_ppe_left(&conn->pkt.ppe) < left); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + /* Make sure that packet is padded */ + CU_ASSERT(1200 == spktlen); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_writev_datagram(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_ssize spktlen; + ngtcp2_tstamp t = 0; + ngtcp2_vec datav = {null_data, 10}; + ngtcp2_vec vec; + int accepted; + my_user_data ud; + ngtcp2_frame fr; + size_t pktlen; + int rv; + + setup_default_client(&conn); + conn->callbacks.ack_datagram = ack_datagram; + conn->remote.transport_params->max_datagram_frame_size = 1 + 1 + 10; + conn->user_data = &ud; + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_NONE, 1000000009, &datav, 1, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(0 != accepted); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + + ud.datagram.dgram_id = 0; + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1000000009 == ud.datagram.dgram_id); + + ngtcp2_conn_del(conn); + + /* Coalesces multiple DATAGRAM frames into a single QUIC packet */ + setup_default_client(&conn); + conn->remote.transport_params->max_datagram_frame_size = 65535; + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_MORE, 1000000007, &datav, 1, ++t); + + CU_ASSERT(NGTCP2_ERR_WRITE_MORE == spktlen); + CU_ASSERT(0 != accepted); + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_MORE, 1000000007, &datav, 1, ++t); + + CU_ASSERT(NGTCP2_ERR_WRITE_MORE == spktlen); + CU_ASSERT(0 != accepted); + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_NONE, 0, &datav, 1, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(0 != accepted); + + ngtcp2_conn_del(conn); + + /* DATAGRAM cannot fit into QUIC packet because the other frames + occupy the space */ + setup_default_client(&conn); + conn->remote.transport_params->max_datagram_frame_size = + 1 + ngtcp2_put_uvarintlen(2000) + 2000; + + vec.base = null_data; + vec.len = 2000; + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_NONE, 987, &vec, 1, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(0 == accepted); + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_NONE, 545, &vec, 1, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(0 != accepted); + + ngtcp2_conn_del(conn); + + /* Calling ngtcp2_conn_writev_datagram without receiving positive + max_datagram_frame_size is an error */ + setup_default_client(&conn); + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_NONE, 999, &datav, 1, ++t); + + CU_ASSERT(NGTCP2_ERR_INVALID_STATE == spktlen); + + ngtcp2_conn_del(conn); + + /* Sending DATAGRAM which is larger than the value of received + max_datagram_frame_size is an error */ + setup_default_client(&conn); + conn->remote.transport_params->max_datagram_frame_size = 9; + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_NONE, 4433, &datav, 1, ++t); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == spktlen); + + ngtcp2_conn_del(conn); + + /* Send DATAGRAM frame in a 0RTT packet */ + setup_early_client(&conn); + + conn->remote.transport_params->max_datagram_frame_size = 4311; + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_NONE, 22360679, &datav, 1, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(0 != accepted); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_datagram(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_frame fr; + size_t pktlen; + int64_t pkt_num = 0; + ngtcp2_tstamp t = 0; + my_user_data ud; + int rv; + ngtcp2_cid rcid; + + rcid_init(&rcid); + + setup_default_server(&conn); + conn->user_data = &ud; + conn->callbacks.recv_datagram = recv_datagram; + conn->local.transport_params.max_datagram_frame_size = 1 + 1111; + + fr.type = NGTCP2_FRAME_DATAGRAM; + fr.datagram.data = fr.datagram.rdata; + fr.datagram.data->base = null_data; + fr.datagram.data->len = 1111; + fr.datagram.datacnt = 1; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1111 == ud.datagram.datalen); + CU_ASSERT(!(NGTCP2_DATAGRAM_FLAG_EARLY & ud.datagram.flags)); + + ngtcp2_conn_del(conn); + + /* Receiving DATAGRAM frame which is strictly larger than the + declared limit is an error */ + setup_default_server(&conn); + conn->local.transport_params.max_datagram_frame_size = 1 + 1111 - 1; + + fr.type = NGTCP2_FRAME_DATAGRAM; + fr.datagram.data = fr.datagram.rdata; + fr.datagram.data->base = null_data; + fr.datagram.data->len = 1111; + fr.datagram.datacnt = 1; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_PROTO == rv); + + ngtcp2_conn_del(conn); + + /* Receiving DATAGRAM frame in a 0RTT packet */ + setup_early_server(&conn); + conn->user_data = &ud; + conn->callbacks.recv_datagram = recv_datagram; + conn->local.transport_params.max_datagram_frame_size = 1 + 1111; + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1199; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_DATAGRAM; + fr.datagram.data = fr.datagram.rdata; + fr.datagram.data->base = null_data; + fr.datagram.data->len = 1111; + fr.datagram.datacnt = 1; + + pktlen = + write_0rtt_pkt(buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), + ++pkt_num, conn->client_chosen_version, &fr, 1, &null_ckm); + + memset(&ud, 0, sizeof(ud)); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1111 == ud.datagram.datalen); + CU_ASSERT(NGTCP2_DATAGRAM_FLAG_EARLY & ud.datagram.flags); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_new_connection_id(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_tstamp t = 0; + int64_t pkt_num = 0; + ngtcp2_frame fr; + ngtcp2_frame frs[16]; + const uint8_t cid[] = {0xf0, 0xf1, 0xf2, 0xf3}; + const uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN] = {0xff}; + const uint8_t cid2[] = {0xf0, 0xf1, 0xf2, 0xf4}; + const uint8_t token2[NGTCP2_STATELESS_RESET_TOKENLEN] = {0xfe}; + const uint8_t cid3[] = {0xf0, 0xf1, 0xf2, 0xf5}; + const uint8_t token3[NGTCP2_STATELESS_RESET_TOKENLEN] = {0xfd}; + ngtcp2_dcid *dcid; + int rv; + ngtcp2_frame_chain *frc; + size_t i; + + setup_default_client(&conn); + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&fr.new_connection_id.cid, cid, sizeof(cid)); + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + + assert(ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + dcid = ngtcp2_ringbuf_get(&conn->dcid.unused.rb, 0); + + CU_ASSERT(ngtcp2_cid_eq(&fr.new_connection_id.cid, &dcid->cid)); + CU_ASSERT(dcid->flags & NGTCP2_DCID_FLAG_TOKEN_PRESENT); + CU_ASSERT(0 == memcmp(fr.new_connection_id.stateless_reset_token, dcid->token, + sizeof(fr.new_connection_id.stateless_reset_token))); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 2; + fr.new_connection_id.retire_prior_to = 2; + ngtcp2_cid_init(&fr.new_connection_id.cid, cid2, sizeof(cid2)); + memcpy(fr.new_connection_id.stateless_reset_token, token2, sizeof(token2)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.bound.rb)); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + CU_ASSERT(2 == conn->dcid.current.seq); + CU_ASSERT(NULL != conn->pktns.tx.frq); + CU_ASSERT(2 == conn->dcid.retire_prior_to); + + frc = conn->pktns.tx.frq; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + + frc = frc->next; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + CU_ASSERT(NULL == frc->next); + + /* This will send RETIRE_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + ngtcp2_conn_del(conn); + + /* Received connection ID is immediately retired due to packet + reordering */ + setup_default_client(&conn); + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 2; + fr.new_connection_id.retire_prior_to = 2; + ngtcp2_cid_init(&fr.new_connection_id.cid, cid, sizeof(cid)); + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + CU_ASSERT(2 == conn->dcid.current.seq); + CU_ASSERT(2 == conn->dcid.retire_prior_to); + + frc = conn->pktns.tx.frq; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + CU_ASSERT(NULL == frc->next); + + /* This will send RETIRE_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&fr.new_connection_id.cid, cid2, sizeof(cid2)); + memcpy(fr.new_connection_id.stateless_reset_token, token2, sizeof(token2)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + CU_ASSERT(2 == conn->dcid.current.seq); + CU_ASSERT(2 == conn->dcid.retire_prior_to); + + frc = conn->pktns.tx.frq; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + CU_ASSERT(NULL == frc->next); + + ngtcp2_conn_del(conn); + + /* ngtcp2_pv contains DCIDs that should be retired. */ + setup_default_server(&conn); + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + assert(NULL == conn->pv); + + frs[0].type = NGTCP2_FRAME_PING; + frs[1].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[1].new_connection_id.seq = 1; + frs[1].new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&frs[1].new_connection_id.cid, cid, sizeof(cid)); + memcpy(frs[1].new_connection_id.stateless_reset_token, token, sizeof(token)); + frs[2].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[2].new_connection_id.seq = 2; + frs[2].new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&frs[2].new_connection_id.cid, cid2, sizeof(cid2)); + memcpy(frs[2].new_connection_id.stateless_reset_token, token2, + sizeof(token2)); + frs[3].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[3].new_connection_id.seq = 3; + frs[3].new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&frs[3].new_connection_id.cid, cid3, sizeof(cid3)); + memcpy(frs[3].new_connection_id.stateless_reset_token, token3, + sizeof(token3)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 4, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + assert(NULL != conn->pv); + + CU_ASSERT(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE); + CU_ASSERT(1 == conn->pv->dcid.seq); + CU_ASSERT(0 == conn->pv->fallback_dcid.seq); + CU_ASSERT(2 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 3; + fr.new_connection_id.retire_prior_to = 2; + ngtcp2_cid_init(&fr.new_connection_id.cid, cid3, sizeof(cid3)); + memcpy(fr.new_connection_id.stateless_reset_token, token3, sizeof(token3)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + CU_ASSERT(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE); + CU_ASSERT(2 == conn->pv->dcid.seq); + CU_ASSERT(3 == conn->pv->fallback_dcid.seq); + + frc = conn->pktns.tx.frq; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + CU_ASSERT(0 == frc->fr.retire_connection_id.seq); + frc = frc->next; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + CU_ASSERT(1 == frc->fr.retire_connection_id.seq); + CU_ASSERT(NULL == frc->next); + + ngtcp2_conn_del(conn); + + /* ngtcp2_pv contains DCID in fallback that should be retired and + there is not enough connection ID left. */ + setup_default_server(&conn); + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + assert(NULL == conn->pv); + + frs[0].type = NGTCP2_FRAME_PING; + frs[1].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[1].new_connection_id.seq = 1; + frs[1].new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&frs[1].new_connection_id.cid, cid, sizeof(cid)); + memcpy(frs[1].new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + assert(NULL != conn->pv); + + CU_ASSERT(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE); + CU_ASSERT(1 == conn->pv->dcid.seq); + CU_ASSERT(0 == conn->pv->fallback_dcid.seq); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 2; + fr.new_connection_id.retire_prior_to = 2; + ngtcp2_cid_init(&fr.new_connection_id.cid, cid2, sizeof(cid2)); + memcpy(fr.new_connection_id.stateless_reset_token, token2, sizeof(token2)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(2 == conn->dcid.current.seq); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + CU_ASSERT(NULL == conn->pv); + + frc = conn->pktns.tx.frq; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + CU_ASSERT(0 == frc->fr.retire_connection_id.seq); + + frc = frc->next; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + CU_ASSERT(1 == frc->fr.retire_connection_id.seq); + CU_ASSERT(NULL == frc->next); + + ngtcp2_conn_del(conn); + + /* ngtcp2_pv contains DCIDs that should be retired and there is not + enough connection ID left to continue path validation. */ + setup_default_server(&conn); + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + assert(NULL == conn->pv); + + frs[0].type = NGTCP2_FRAME_PING; + frs[1].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[1].new_connection_id.seq = 1; + frs[1].new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&frs[1].new_connection_id.cid, cid, sizeof(cid)); + memcpy(frs[1].new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + assert(NULL != conn->pv); + + CU_ASSERT(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE); + CU_ASSERT(1 == conn->pv->dcid.seq); + CU_ASSERT(0 == conn->pv->fallback_dcid.seq); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + + /* Overwrite seq in pv->dcid so that pv->dcid cannot be renewed. */ + conn->pv->dcid.seq = 2; + /* Internally we assume that if primary dcid and pv->dcid differ, + then no fallback dcid is present. */ + conn->pv->flags &= (uint8_t)~NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE; + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 3; + fr.new_connection_id.retire_prior_to = 3; + ngtcp2_cid_init(&fr.new_connection_id.cid, cid3, sizeof(cid3)); + memcpy(fr.new_connection_id.stateless_reset_token, token3, sizeof(token3)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(3 == conn->dcid.current.seq); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + CU_ASSERT(NULL == conn->pv); + + frc = conn->pktns.tx.frq; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + CU_ASSERT(2 == frc->fr.retire_connection_id.seq); + + frc = frc->next; + + CU_ASSERT(NGTCP2_FRAME_RETIRE_CONNECTION_ID == frc->fr.type); + CU_ASSERT(1 == frc->fr.retire_connection_id.seq); + CU_ASSERT(NULL == frc->next); + + ngtcp2_conn_del(conn); + + /* Receiving more than advertised CID is treated as error */ + setup_default_server(&conn); + conn->local.transport_params.active_connection_id_limit = 2; + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + assert(NULL == conn->pv); + + frs[0].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[0].new_connection_id.seq = 1; + frs[0].new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&frs[0].new_connection_id.cid, cid, sizeof(cid)); + memcpy(frs[0].new_connection_id.stateless_reset_token, token, sizeof(token)); + frs[1].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[1].new_connection_id.seq = 2; + frs[1].new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&frs[1].new_connection_id.cid, cid2, sizeof(cid2)); + memcpy(frs[1].new_connection_id.stateless_reset_token, token2, + sizeof(token2)); + frs[2].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[2].new_connection_id.seq = 3; + frs[2].new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&frs[2].new_connection_id.cid, cid3, sizeof(cid3)); + memcpy(frs[2].new_connection_id.stateless_reset_token, token3, + sizeof(token3)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 3, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_CONNECTION_ID_LIMIT == rv); + + ngtcp2_conn_del(conn); + + /* Receiving duplicated NEW_CONNECTION_ID frame */ + setup_default_server(&conn); + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + frs[0].type = NGTCP2_FRAME_PING; + + frs[1].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[1].new_connection_id.seq = 1; + frs[1].new_connection_id.retire_prior_to = 1; + ngtcp2_cid_init(&frs[1].new_connection_id.cid, cid, sizeof(cid)); + memcpy(frs[1].new_connection_id.stateless_reset_token, token, sizeof(token)); + + frs[2].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[2].new_connection_id.seq = 2; + frs[2].new_connection_id.retire_prior_to = 1; + ngtcp2_cid_init(&frs[2].new_connection_id.cid, cid2, sizeof(cid2)); + memcpy(frs[2].new_connection_id.stateless_reset_token, token2, + sizeof(token2)); + + frs[3].type = NGTCP2_FRAME_PADDING; + frs[3].padding.len = 1200; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 4, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + CU_ASSERT(2 == conn->dcid.current.seq); + CU_ASSERT(NULL != conn->pv); + CU_ASSERT(ngtcp2_cid_eq(&frs[1].new_connection_id.cid, + &conn->pv->fallback_dcid.cid)); + + /* This will send PATH_CHALLENGE frame */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen >= 1200); + + fr.type = NGTCP2_FRAME_PATH_RESPONSE; + memset(fr.path_response.data, 0, sizeof(fr.path_response.data)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + /* Server starts probing old path */ + CU_ASSERT(NULL != conn->pv); + CU_ASSERT(ngtcp2_path_eq(&null_path.path, &conn->pv->dcid.ps.path)); + + /* Receive NEW_CONNECTION_ID seq=1 again, which should be ignored. */ + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.unused.rb)); + CU_ASSERT(2 == conn->dcid.current.seq); + + ngtcp2_conn_del(conn); + + /* Exceeding the limit for the number of unacknowledged + RETIRE_CONNECTION_ID leads to NGTCP2_ERR_CONNECTION_ID_LIMIT. */ + setup_default_server(&conn); + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + for (i = 0; i < 7; ++i) { + frs[i].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[i].new_connection_id.seq = i + 1; + frs[i].new_connection_id.retire_prior_to = 0; + ngtcp2_cid_init(&frs[i].new_connection_id.cid, cid, sizeof(cid)); + frs[i].new_connection_id.cid.data[0] = (uint8_t)i; + memcpy(frs[i].new_connection_id.stateless_reset_token, token, + sizeof(token)); + } + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 7, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + for (i = 0; i < 8; ++i) { + frs[i].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[i].new_connection_id.seq = i + 8; + frs[i].new_connection_id.retire_prior_to = 8; + ngtcp2_cid_init(&frs[i].new_connection_id.cid, cid, sizeof(cid)); + frs[i].new_connection_id.cid.data[0] = (uint8_t)(i + 8); + memcpy(frs[i].new_connection_id.stateless_reset_token, token, + sizeof(token)); + } + + for (i = 0; i < 8; ++i) { + frs[i + 8].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[i + 8].new_connection_id.seq = i + 16; + frs[i + 8].new_connection_id.retire_prior_to = 16; + ngtcp2_cid_init(&frs[i + 8].new_connection_id.cid, cid, sizeof(cid)); + frs[i + 8].new_connection_id.cid.data[0] = (uint8_t)(i + 16); + memcpy(frs[i + 8].new_connection_id.stateless_reset_token, token, + sizeof(token)); + } + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 16, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + frs[0].type = NGTCP2_FRAME_NEW_CONNECTION_ID; + frs[0].new_connection_id.seq = 24; + frs[0].new_connection_id.retire_prior_to = 17; + ngtcp2_cid_init(&frs[0].new_connection_id.cid, cid, sizeof(cid)); + frs[0].new_connection_id.cid.data[0] = (uint8_t)(i + 24); + memcpy(frs[0].new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_CONNECTION_ID_LIMIT == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_retire_connection_id(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_tstamp t = 1000000009; + int64_t pkt_num = 0; + ngtcp2_frame fr; + int rv; + ngtcp2_ksl_it it; + ngtcp2_scid *scid; + uint64_t seq; + + setup_default_client(&conn); + conn->remote.transport_params->active_connection_id_limit = 7; + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), t); + + CU_ASSERT(spktlen > 0); + + it = ngtcp2_ksl_begin(&conn->scid.set); + scid = ngtcp2_ksl_it_get(&it); + seq = scid->seq; + + CU_ASSERT(NGTCP2_SCID_FLAG_NONE == scid->flags); + CU_ASSERT(UINT64_MAX == scid->retired_ts); + CU_ASSERT(1 == ngtcp2_pq_size(&conn->scid.used)); + + fr.type = NGTCP2_FRAME_RETIRE_CONNECTION_ID; + fr.retire_connection_id.seq = seq; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_SCID_FLAG_RETIRED == scid->flags); + CU_ASSERT(1000000010 == scid->retired_ts); + CU_ASSERT(2 == ngtcp2_pq_size(&conn->scid.used)); + CU_ASSERT(7 == ngtcp2_ksl_len(&conn->scid.set)); + CU_ASSERT(1 == conn->scid.num_retired); + + /* One NEW_CONNECTION_ID frame is sent as a replacement. */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(8 == ngtcp2_ksl_len(&conn->scid.set)); + CU_ASSERT(1 == conn->scid.num_retired); + + /* No NEW_CONNECTION_ID frames should be sent. */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen == 0); + CU_ASSERT(8 == ngtcp2_ksl_len(&conn->scid.set)); + CU_ASSERT(1 == conn->scid.num_retired); + + /* Now time passed and retired connection ID is removed */ + t += 7 * NGTCP2_DEFAULT_INITIAL_RTT; + + ngtcp2_conn_handle_expiry(conn, t); + + CU_ASSERT(7 == ngtcp2_ksl_len(&conn->scid.set)); + CU_ASSERT(0 == conn->scid.num_retired); + + ngtcp2_conn_del(conn); + + /* Receiving RETIRE_CONNECTION_ID with seq which is greater than the + sequence number previously sent must be treated as error */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_RETIRE_CONNECTION_ID; + fr.retire_connection_id.seq = 1; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_PROTO == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_server_path_validation(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_tstamp t = 900; + int64_t pkt_num = 0; + ngtcp2_frame fr; + int rv; + const uint8_t raw_cid[] = {0x0f, 0x00, 0x00, 0x00}; + ngtcp2_cid cid, *new_cid; + const uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN] = {0xff}; + ngtcp2_path_storage new_path1, new_path2; + ngtcp2_ksl_it it; + + path_init(&new_path1, 0, 0, 2, 0); + path_init(&new_path2, 0, 0, 3, 0); + + ngtcp2_cid_init(&cid, raw_cid, sizeof(raw_cid)); + + setup_default_server(&conn); + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(ngtcp2_ksl_len(&conn->scid.set) > 1); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + fr.new_connection_id.cid = cid; + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_PING; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path1.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->pv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(ngtcp2_ringbuf_len(&conn->pv->ents.rb) > 0); + + fr.type = NGTCP2_FRAME_PATH_RESPONSE; + memset(fr.path_response.data, 0, sizeof(fr.path_response.data)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path1.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_path_eq(&new_path1.path, &conn->dcid.current.ps.path)); + /* DCID does not change because the client does not change its + DCID. */ + CU_ASSERT(!ngtcp2_cid_eq(&cid, &conn->dcid.current.cid)); + + /* A remote endpoint changes DCID as well */ + fr.type = NGTCP2_FRAME_PING; + + it = ngtcp2_ksl_begin(&conn->scid.set); + + assert(!ngtcp2_ksl_it_end(&it)); + + new_cid = &(((ngtcp2_scid *)ngtcp2_ksl_it_get(&it))->cid); + + pktlen = write_pkt(buf, sizeof(buf), new_cid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path2.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->pv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(ngtcp2_ringbuf_len(&conn->pv->ents.rb) > 0); + + fr.type = NGTCP2_FRAME_PATH_RESPONSE; + memset(fr.path_response.data, 0, sizeof(fr.path_response.data)); + + pktlen = write_pkt(buf, sizeof(buf), new_cid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path2.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_path_eq(&new_path2.path, &conn->dcid.current.ps.path)); + CU_ASSERT(ngtcp2_cid_eq(&cid, &conn->dcid.current.cid)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_client_connection_migration(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_tstamp t = 900; + int64_t pkt_num = 0; + ngtcp2_frame fr; + int rv; + const uint8_t raw_cid[] = {0x0f, 0x00, 0x00, 0x00}; + ngtcp2_cid cid; + const uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN] = {0xff}; + my_user_data ud; + ngtcp2_ssize spktlen; + ngtcp2_path_storage to_path; + + ngtcp2_cid_init(&cid, raw_cid, sizeof(raw_cid)); + + /* immediate migration */ + setup_default_client(&conn); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + fr.new_connection_id.cid = cid; + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + ngtcp2_path_storage_init2(&to_path, &new_path.path); + to_path.path.user_data = &ud; + + rv = ngtcp2_conn_initiate_immediate_migration(conn, &to_path.path, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->pv); + CU_ASSERT(ngtcp2_path_eq(&to_path.path, &conn->dcid.current.ps.path)); + CU_ASSERT(&ud == conn->dcid.current.ps.path.user_data); + CU_ASSERT(ngtcp2_cid_eq(&cid, &conn->dcid.current.cid)); + CU_ASSERT(ngtcp2_path_eq(&to_path.path, &conn->pv->dcid.ps.path)); + CU_ASSERT(&ud == conn->pv->dcid.ps.path.user_data); + CU_ASSERT(ngtcp2_cid_eq(&cid, &conn->pv->dcid.cid)); + + ngtcp2_conn_del(conn); + + /* migrate after successful path validation */ + setup_default_client(&conn); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + fr.new_connection_id.cid = cid; + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + ngtcp2_path_storage_init2(&to_path, &new_path.path); + to_path.path.user_data = &ud; + + rv = ngtcp2_conn_initiate_migration(conn, &to_path.path, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->pv); + CU_ASSERT(ngtcp2_path_eq(&null_path.path, &conn->dcid.current.ps.path)); + CU_ASSERT(NULL == conn->dcid.current.ps.path.user_data); + CU_ASSERT(ngtcp2_cid_eq(&conn->rcid, &conn->dcid.current.cid)); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 < spktlen); + + fr.type = NGTCP2_FRAME_PATH_RESPONSE; + memset(fr.path_response.data, 0, sizeof(fr.path_response.data)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == conn->pv); + CU_ASSERT(ngtcp2_path_eq(&to_path.path, &conn->dcid.current.ps.path)); + CU_ASSERT(&ud == conn->dcid.current.ps.path.user_data); + CU_ASSERT(ngtcp2_cid_eq(&cid, &conn->dcid.current.cid)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_path_challenge(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_tstamp t = 11; + int64_t pkt_num = 0; + ngtcp2_frame fr; + ngtcp2_frame frs[2]; + int rv; + const uint8_t raw_cid[] = {0x0f, 0x00, 0x00, 0x00}; + ngtcp2_cid cid; + const uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN] = {0xff}; + const uint8_t data[] = {0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8}; + const uint8_t data2[] = {0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf9}; + ngtcp2_path_storage ps; + ngtcp2_ssize shdlen; + ngtcp2_pkt_hd hd; + ngtcp2_dcid *dcid; + ngtcp2_settings settings; + ngtcp2_transport_params params; + + ngtcp2_cid_init(&cid, raw_cid, sizeof(raw_cid)); + + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + fr.new_connection_id.cid = cid; + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + frs[0].type = NGTCP2_FRAME_PATH_CHALLENGE; + memcpy(frs[0].path_challenge.data, data, sizeof(frs[0].path_challenge.data)); + frs[1].type = NGTCP2_FRAME_PADDING; + frs[1].padding.len = 1200; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb) > 0); + + ngtcp2_path_storage_zero(&ps); + + spktlen = ngtcp2_conn_write_pkt(conn, &ps.path, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen >= 1200); + CU_ASSERT(ngtcp2_path_eq(&new_path.path, &ps.path)); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb)); + CU_ASSERT(1 == ngtcp2_ringbuf_len(&conn->dcid.bound.rb)); + + dcid = ngtcp2_ringbuf_get(&conn->dcid.bound.rb, 0); + + CU_ASSERT((uint64_t)spktlen == dcid->bytes_sent); + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, buf, (size_t)spktlen, cid.datalen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(ngtcp2_cid_eq(&cid, &hd.dcid)); + + /* Use same bound DCID for PATH_CHALLENGE from the same path. */ + fr.type = NGTCP2_FRAME_PATH_CHALLENGE; + memcpy(fr.path_challenge.data, data2, sizeof(fr.path_challenge.data)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb) > 0); + + ngtcp2_path_storage_zero(&ps); + + spktlen = ngtcp2_conn_write_pkt(conn, &ps.path, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(ngtcp2_path_eq(&new_path.path, &ps.path)); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb)); + CU_ASSERT(1 == ngtcp2_ringbuf_len(&conn->dcid.bound.rb)); + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, buf, (size_t)spktlen, cid.datalen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(ngtcp2_cid_eq(&cid, &hd.dcid)); + + ngtcp2_conn_del(conn); + + /* PATH_CHALLENGE from the current path */ + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + fr.new_connection_id.cid = cid; + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + frs[0].type = NGTCP2_FRAME_PATH_CHALLENGE; + memcpy(frs[0].path_challenge.data, data, sizeof(frs[0].path_challenge.data)); + frs[1].type = NGTCP2_FRAME_PADDING; + frs[1].padding.len = 1200; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb) > 0); + + ngtcp2_path_storage_zero(&ps); + + spktlen = ngtcp2_conn_write_pkt(conn, &ps.path, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen >= 1200); + CU_ASSERT(ngtcp2_path_eq(&null_path.path, &ps.path)); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb)); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.bound.rb)); + CU_ASSERT((uint64_t)spktlen == conn->dcid.current.bytes_sent); + + ngtcp2_conn_del(conn); + + /* PATH_CHALLENGE should be ignored with server + disable_active_migration */ + setup_default_server(&conn); + + conn->local.transport_params.disable_active_migration = 1; + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + fr.new_connection_id.cid = cid; + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + frs[0].type = NGTCP2_FRAME_PATH_CHALLENGE; + memcpy(frs[0].path_challenge.data, data, sizeof(frs[0].path_challenge.data)); + frs[1].type = NGTCP2_FRAME_PADDING; + frs[1].padding.len = 1200; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb)); + + ngtcp2_conn_del(conn); + + /* PATH_CHALLENGE on NAT rebinding (passive migration) should be + accepted with server disable_active_migration */ + setup_default_server(&conn); + + conn->local.transport_params.disable_active_migration = 1; + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + fr.new_connection_id.cid = cid; + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + frs[0].type = NGTCP2_FRAME_PATH_CHALLENGE; + memcpy(frs[0].path_challenge.data, data, sizeof(frs[0].path_challenge.data)); + frs[1].type = NGTCP2_FRAME_PADDING; + frs[1].padding.len = 1200; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_nat_path.path, &null_pi, buf, pktlen, + ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb) > 0); + + ngtcp2_conn_del(conn); + + /* PATH_CHALLENGE to preferred address should be accepted with + server disable_active_migration */ + server_default_transport_params(¶ms); + params.disable_active_migration = 1; + params.preferred_address_present = 1; + params.preferred_address.cid = cid; + + /* Set local address of new_path */ + assert(AF_INET == new_path.path.local.addr->sa_family); + + params.preferred_address.ipv4_present = 1; + memcpy(¶ms.preferred_address.ipv4, new_path.path.local.addr, + sizeof(params.preferred_address.ipv4)); + + server_default_settings(&settings); + + setup_default_server_settings(&conn, &null_path.path, &settings, ¶ms); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + fr.new_connection_id.cid = cid; + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + frs[0].type = NGTCP2_FRAME_PATH_CHALLENGE; + memcpy(frs[0].path_challenge.data, data, sizeof(frs[0].path_challenge.data)); + frs[1].type = NGTCP2_FRAME_PADDING; + frs[1].padding.len = 1200; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb) > 0); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_key_update(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_tstamp t = 19393; + int64_t pkt_num = -1; + ngtcp2_frame fr; + int rv; + int64_t stream_id; + ngtcp2_ssize nwrite; + + setup_default_server(&conn); + + /* The remote endpoint initiates key update */ + fr.type = NGTCP2_FRAME_PING; + + pktlen = + write_pkt_flags(buf, sizeof(buf), NGTCP2_PKT_FLAG_KEY_PHASE, &conn->oscid, + ++pkt_num, &fr, 1, conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->crypto.key_update.old_rx_ckm); + CU_ASSERT(NULL == conn->crypto.key_update.new_tx_ckm); + CU_ASSERT(NULL == conn->crypto.key_update.new_rx_ckm); + CU_ASSERT(UINT64_MAX == conn->crypto.key_update.confirmed_ts); + CU_ASSERT(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED); + CU_ASSERT(!(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR)); + + t += NGTCP2_SECONDS; + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(t == conn->crypto.key_update.confirmed_ts); + CU_ASSERT(!(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED)); + CU_ASSERT(!(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR)); + + t += ngtcp2_conn_get_pto(conn) + 1; + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), t); + + CU_ASSERT(0 == spktlen); + CU_ASSERT(NULL == conn->crypto.key_update.old_rx_ckm); + CU_ASSERT(NULL != conn->crypto.key_update.new_tx_ckm); + CU_ASSERT(NULL != conn->crypto.key_update.new_rx_ckm); + + /* The local endpoint initiates key update */ + t += ngtcp2_conn_get_pto(conn) * 2; + + rv = ngtcp2_conn_initiate_key_update(conn, t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->crypto.key_update.old_rx_ckm); + CU_ASSERT(NULL == conn->crypto.key_update.new_tx_ckm); + CU_ASSERT(NULL == conn->crypto.key_update.new_rx_ckm); + CU_ASSERT(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED); + CU_ASSERT(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR); + + rv = ngtcp2_conn_open_uni_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = + write_pkt_flags(buf, sizeof(buf), NGTCP2_PKT_FLAG_KEY_PHASE, &conn->oscid, + ++pkt_num, &fr, 1, conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(t == conn->crypto.key_update.confirmed_ts); + CU_ASSERT(!(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_NOT_CONFIRMED)); + CU_ASSERT(!(conn->flags & NGTCP2_CONN_FLAG_KEY_UPDATE_INITIATOR)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_crypto_buffer_exceeded(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_tstamp t = 11111; + int64_t pkt_num = -1; + ngtcp2_frame fr; + int rv; + + setup_default_client(&conn); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 1000000; + fr.crypto.datacnt = 1; + fr.crypto.data[0].base = null_data; + fr.crypto.data[0].len = 1; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_CRYPTO_BUFFER_EXCEEDED == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_handshake_probe(void) { + ngtcp2_conn *conn; + ngtcp2_tstamp t = 0; + ngtcp2_ssize spktlen; + size_t pktlen; + uint8_t buf[1200]; + ngtcp2_frame fr; + ngtcp2_rtb_entry *ent; + ngtcp2_ksl_it it; + int rv; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + + /* Retransmit first Initial on PTO timer */ + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1 == conn->in_pktns->rtb.num_ack_eliciting); + + rv = ngtcp2_conn_on_loss_detection_timer(conn, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == conn->in_pktns->rtb.probe_pkt_left); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(1 == conn->in_pktns->rtb.num_retransmittable); + CU_ASSERT(2 == conn->in_pktns->rtb.num_ack_eliciting); + CU_ASSERT(0 == conn->in_pktns->rtb.probe_pkt_left); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = 0; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_initial_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), 0, NGTCP2_PROTO_VER_V1, + NULL, 0, &fr, 1, &null_ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == conn->in_pktns->rtb.num_ack_eliciting); + + rv = ngtcp2_conn_on_loss_detection_timer(conn, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == conn->in_pktns->rtb.num_retransmittable); + CU_ASSERT(1 == conn->in_pktns->rtb.num_ack_eliciting); + CU_ASSERT(1 == conn->in_pktns->rtb.probe_pkt_left); + + /* This sends anti-deadlock padded Initial packet even if we have + nothing to send. */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(0 == conn->in_pktns->rtb.num_retransmittable); + CU_ASSERT(2 == conn->in_pktns->rtb.num_ack_eliciting); + CU_ASSERT(0 == conn->in_pktns->rtb.probe_pkt_left); + + it = ngtcp2_rtb_head(&conn->in_pktns->rtb); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PROBE); + CU_ASSERT(sizeof(buf) == ent->pktlen); + + init_crypto_ctx(&crypto_ctx); + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + rv = ngtcp2_conn_on_loss_detection_timer(conn, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(2 == conn->in_pktns->rtb.num_ack_eliciting); + CU_ASSERT(1 == conn->hs_pktns->rtb.probe_pkt_left); + + /* This sends anti-deadlock Handshake packet even if we have nothing + to send. */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(0 == conn->hs_pktns->rtb.num_retransmittable); + CU_ASSERT(1 == conn->hs_pktns->rtb.num_ack_eliciting); + CU_ASSERT(0 == conn->hs_pktns->rtb.probe_pkt_left); + + it = ngtcp2_rtb_head(&conn->hs_pktns->rtb); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(ent->flags & NGTCP2_RTB_ENTRY_FLAG_PROBE); + CU_ASSERT(sizeof(buf) > ent->pktlen); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_handshake_loss(void) { + ngtcp2_conn *conn; + ngtcp2_tstamp t = 0; + ngtcp2_ssize spktlen; + size_t i; + size_t pktlen; + uint8_t buf[1252]; + ngtcp2_frame fr; + ngtcp2_frame frs[2]; + ngtcp2_cid rcid; + int rv; + int64_t pkt_num = -1; + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + + rcid_init(&rcid); + setup_handshake_server(&conn); + conn->callbacks.recv_crypto_data = recv_crypto_data; + + frs[0].type = NGTCP2_FRAME_CRYPTO; + frs[0].crypto.offset = 0; + frs[0].crypto.datacnt = 1; + frs[0].crypto.data[0].len = 123; + frs[0].crypto.data[0].base = null_data; + + frs[1].type = NGTCP2_FRAME_PADDING; + frs[1].padding.len = 1005; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, frs, 2, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + /* Increase anti-amplification factor for easier testing */ + conn->dcid.current.bytes_recv += 10000; + + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_INITIAL, null_data, + 123); + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_HANDSHAKE, null_data, + 163); + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_HANDSHAKE, null_data, + 2369); + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_HANDSHAKE, null_data, + 79); + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_HANDSHAKE, null_data, + 36); + + /* Initial and first Handshake are coalesced into 1 packet. */ + for (i = 0; i < 3; ++i) { + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + CU_ASSERT(spktlen > 0); + } + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 == spktlen); + + t += 30 * NGTCP2_MILLISECONDS; + + ngtcp2_conn_on_loss_detection_timer(conn, t); + + CU_ASSERT(1 == conn->in_pktns->rtb.probe_pkt_left); + CU_ASSERT(1 == conn->hs_pktns->rtb.probe_pkt_left); + + /* Send a PTO probe packet */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 == spktlen); + + it = ngtcp2_ksl_begin(&conn->hs_pktns->rtb.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(0 == ent->frc->fr.crypto.offset); + CU_ASSERT(987 == ngtcp2_vec_len(ent->frc->fr.crypto.data, + ent->frc->fr.crypto.datacnt)); + CU_ASSERT(3 == ent->hd.pkt_num); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = 2; + fr.ack.ack_delay = 0; + fr.ack.ack_delay_unscaled = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_handshake_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, t); + + CU_ASSERT(0 == rv); + + t += 40 * NGTCP2_MILLISECONDS; + + ngtcp2_conn_on_loss_detection_timer(conn, t); + + CU_ASSERT(0 == conn->hs_pktns->rtb.probe_pkt_left); + + /* Retransmits the contents of lost packet */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + it = ngtcp2_ksl_begin(&conn->hs_pktns->rtb.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(NGTCP2_FRAME_CRYPTO == ent->frc->fr.type); + CU_ASSERT(987 == ent->frc->fr.crypto.offset); + CU_ASSERT(1 == ent->frc->fr.crypto.datacnt); + CU_ASSERT(1183 == ngtcp2_vec_len(ent->frc->fr.crypto.data, + ent->frc->fr.crypto.datacnt)); + CU_ASSERT(4 == ent->hd.pkt_num); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 == spktlen); + + t += 30 * NGTCP2_MILLISECONDS; + + ngtcp2_conn_on_loss_detection_timer(conn, t); + + CU_ASSERT(1 == conn->hs_pktns->rtb.probe_pkt_left); + + /* Send a PTO probe packet */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + it = ngtcp2_ksl_begin(&conn->hs_pktns->rtb.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(NGTCP2_FRAME_CRYPTO == ent->frc->fr.type); + CU_ASSERT(0 == ent->frc->fr.crypto.offset); + CU_ASSERT(2 == ent->frc->fr.crypto.datacnt); + CU_ASSERT(987 == ngtcp2_vec_len(ent->frc->fr.crypto.data, + ent->frc->fr.crypto.datacnt)); + CU_ASSERT(5 == ent->hd.pkt_num); + + ngtcp2_conn_del(conn); + + /* Retransmission splits CRYPTO frame */ + setup_handshake_server(&conn); + conn->callbacks.recv_crypto_data = recv_crypto_data; + + frs[0].type = NGTCP2_FRAME_CRYPTO; + frs[0].crypto.offset = 0; + frs[0].crypto.datacnt = 1; + frs[0].crypto.data[0].len = 123; + frs[0].crypto.data[0].base = null_data; + + frs[1].type = NGTCP2_FRAME_PADDING; + frs[1].padding.len = 1005; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, frs, 2, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + /* Increase anti-amplification factor for easier testing */ + conn->dcid.current.bytes_recv += 10000; + + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_INITIAL, null_data, + 123); + ngtcp2_conn_submit_crypto_data(conn, NGTCP2_CRYPTO_LEVEL_HANDSHAKE, null_data, + 3000); + /* Initial and first Handshake are coalesced into 1 packet. */ + for (i = 0; i < 3; ++i) { + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + CU_ASSERT(spktlen > 0); + } + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 == spktlen); + + it = ngtcp2_ksl_begin(&conn->hs_pktns->rtb.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(NGTCP2_FRAME_CRYPTO == ent->frc->fr.type); + CU_ASSERT(2170 == ent->frc->fr.crypto.offset); + CU_ASSERT(830 == ngtcp2_vec_len(ent->frc->fr.crypto.data, + ent->frc->fr.crypto.datacnt)); + CU_ASSERT(2 == ent->hd.pkt_num); + + t += 30 * NGTCP2_MILLISECONDS; + + ngtcp2_conn_on_loss_detection_timer(conn, t); + + CU_ASSERT(1 == conn->in_pktns->rtb.probe_pkt_left); + CU_ASSERT(1 == conn->hs_pktns->rtb.probe_pkt_left); + + /* 1st PTO */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + CU_ASSERT(spktlen > 0); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 == spktlen); + + it = ngtcp2_ksl_begin(&conn->hs_pktns->rtb.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(NGTCP2_FRAME_CRYPTO == ent->frc->fr.type); + CU_ASSERT(0 == ent->frc->fr.crypto.offset); + CU_ASSERT(987 == ngtcp2_vec_len(ent->frc->fr.crypto.data, + ent->frc->fr.crypto.datacnt)); + CU_ASSERT(3 == ent->hd.pkt_num); + + t += 30 * NGTCP2_MILLISECONDS; + + ngtcp2_conn_on_loss_detection_timer(conn, t); + + CU_ASSERT(1 == conn->in_pktns->rtb.probe_pkt_left); + CU_ASSERT(1 == conn->hs_pktns->rtb.probe_pkt_left); + + /* 2nd PTO. Initial and Handshake packets are coalesced. Handshake + CRYPTO is split into 2 because of Initial CRYPTO. */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + CU_ASSERT(spktlen > 0); + + it = ngtcp2_ksl_begin(&conn->hs_pktns->rtb.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(NGTCP2_FRAME_CRYPTO == ent->frc->fr.type); + CU_ASSERT(987 == ent->frc->fr.crypto.offset); + CU_ASSERT(991 == ngtcp2_vec_len(ent->frc->fr.crypto.data, + ent->frc->fr.crypto.datacnt)); + CU_ASSERT(4 == ent->hd.pkt_num); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + CU_ASSERT(spktlen > 0); + + it = ngtcp2_ksl_begin(&conn->hs_pktns->rtb.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(NGTCP2_FRAME_CRYPTO == ent->frc->fr.type); + CU_ASSERT(1978 == ent->frc->fr.crypto.offset); + CU_ASSERT(192 == ngtcp2_vec_len(ent->frc->fr.crypto.data, + ent->frc->fr.crypto.datacnt)); + CU_ASSERT(5 == ent->hd.pkt_num); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + CU_ASSERT(0 == spktlen); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = 0; + fr.ack.ack_delay = 0; + fr.ack.ack_delay_unscaled = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_handshake_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, &fr, 1, &null_ckm); + + t += NGTCP2_MILLISECONDS; + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, t); + + CU_ASSERT(0 == rv); + + t += 40 * NGTCP2_MILLISECONDS; + + ngtcp2_conn_on_loss_detection_timer(conn, t); + + CU_ASSERT(1 == conn->hs_pktns->rtb.probe_pkt_left); + + /* 3rd PTO */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + CU_ASSERT(0 == spktlen); + + it = ngtcp2_ksl_begin(&conn->hs_pktns->rtb.ents); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(NGTCP2_FRAME_CRYPTO == ent->frc->fr.type); + CU_ASSERT(2170 == ent->frc->fr.crypto.offset); + CU_ASSERT(830 == ngtcp2_vec_len(ent->frc->fr.crypto.data, + ent->frc->fr.crypto.datacnt)); + CU_ASSERT(6 == ent->hd.pkt_num); + CU_ASSERT(NULL == ent->frc->next); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_client_initial_retry(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_frame fr; + int64_t pkt_num = -1; + ngtcp2_tstamp t = 0; + ngtcp2_cid rcid; + int rv; + + rcid_init(&rcid); + + setup_handshake_server(&conn); + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 1; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1245; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_RETRY == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_client_initial_token(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_frame fr; + int64_t pkt_num = -1; + ngtcp2_tstamp t = 0; + ngtcp2_cid rcid; + int rv; + const uint8_t raw_token[] = {0xff, 0x12, 0x31, 0x04, 0xab}; + ngtcp2_vec token; + const ngtcp2_mem *mem; + + rcid_init(&rcid); + + setup_handshake_server(&conn); + mem = conn->mem; + + token.base = ngtcp2_mem_malloc(mem, sizeof(raw_token)); + memcpy(token.base, raw_token, sizeof(raw_token)); + token.len = sizeof(raw_token); + + conn->local.settings.token = token; + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1181; + fr.crypto.data[0].base = null_data; + + pktlen = + write_initial_pkt(buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), + ++pkt_num, conn->client_chosen_version, raw_token, + sizeof(raw_token), &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1181 == ngtcp2_strm_rx_offset(&conn->in_pktns->crypto.strm)); + + ngtcp2_conn_del(conn); + + /* Specifying invalid token lets server drop the packet */ + setup_handshake_server(&conn); + mem = conn->mem; + + token.base = ngtcp2_mem_malloc(mem, sizeof(raw_token)); + memcpy(token.base, raw_token, sizeof(raw_token)); + token.len = sizeof(raw_token) - 1; + + conn->local.settings.token = token; + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1179; + fr.crypto.data[0].base = null_data; + + pktlen = + write_initial_pkt(buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), + ++pkt_num, conn->client_chosen_version, raw_token, + sizeof(raw_token), &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_DROP_CONN == rv); + CU_ASSERT(0 == ngtcp2_strm_rx_offset(&conn->in_pktns->crypto.strm)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_get_active_dcid(void) { + ngtcp2_conn *conn; + ngtcp2_cid_token cid_token[2]; + ngtcp2_cid dcid; + static uint8_t token[] = {0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, + 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1}; + + dcid_init(&dcid); + setup_default_client(&conn); + + CU_ASSERT(1 == ngtcp2_conn_get_num_active_dcid(conn)); + CU_ASSERT(1 == ngtcp2_conn_get_active_dcid(conn, cid_token)); + CU_ASSERT(0 == cid_token[0].seq); + CU_ASSERT(ngtcp2_cid_eq(&dcid, &cid_token[0].cid)); + CU_ASSERT(ngtcp2_path_eq(&null_path.path, &cid_token[0].ps.path)); + CU_ASSERT(1 == cid_token[0].token_present); + CU_ASSERT(0 == + memcmp(token, cid_token[0].token, NGTCP2_STATELESS_RESET_TOKENLEN)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_recv_version_negotiation(void) { + ngtcp2_conn *conn; + const ngtcp2_cid *dcid; + ngtcp2_ssize spktlen; + uint8_t buf[1500]; + uint32_t nsv[3]; + int rv; + ngtcp2_tstamp t = 0; + + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + dcid = ngtcp2_conn_get_dcid(conn); + + nsv[0] = 0xffffffff; + + spktlen = ngtcp2_pkt_write_version_negotiation( + buf, sizeof(buf), 0xfe, conn->oscid.data, conn->oscid.datalen, dcid->data, + dcid->datalen, nsv, 1); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_RECV_VERSION_NEGOTIATION == rv); + + ngtcp2_conn_del(conn); + + /* Ignore Version Negotiation if it contains version selected by + client */ + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + dcid = ngtcp2_conn_get_dcid(conn); + + nsv[0] = 0xfffffff0; + nsv[1] = conn->client_chosen_version; + + spktlen = ngtcp2_pkt_write_version_negotiation( + buf, sizeof(buf), 0x50, conn->oscid.data, conn->oscid.datalen, dcid->data, + dcid->datalen, nsv, 2); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, ++t); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); + + /* Ignore Version Negotiation if client reacted upon Version + Negotiation */ + setup_handshake_client(&conn); + + conn->local.settings.original_version = NGTCP2_PROTO_VER_V2_DRAFT; + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + dcid = ngtcp2_conn_get_dcid(conn); + + nsv[0] = 0xffffffff; + + spktlen = ngtcp2_pkt_write_version_negotiation( + buf, sizeof(buf), 0xfe, conn->oscid.data, conn->oscid.datalen, dcid->data, + dcid->datalen, nsv, 1); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + (size_t)spktlen, ++t); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_send_initial_token(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_callbacks cb; + ngtcp2_settings settings; + ngtcp2_transport_params params; + ngtcp2_cid rcid, scid; + ngtcp2_crypto_aead retry_aead = {0, NGTCP2_FAKE_AEAD_OVERHEAD}; + uint8_t token[] = "this is token"; + ngtcp2_ssize spktlen, shdlen; + ngtcp2_tstamp t = 0; + ngtcp2_pkt_hd hd; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + + rcid_init(&rcid); + scid_init(&scid); + + init_initial_crypto_ctx(&crypto_ctx); + + client_default_callbacks(&cb); + client_default_settings(&settings); + client_default_transport_params(¶ms); + + settings.token.base = token; + settings.token.len = sizeof(token); + + ngtcp2_conn_client_new(&conn, &rcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + /* mem = */ NULL, NULL); + ngtcp2_conn_set_initial_crypto_ctx(conn, &crypto_ctx); + ngtcp2_conn_install_initial_key(conn, &aead_ctx, null_iv, &hp_ctx, &aead_ctx, + null_iv, &hp_ctx, sizeof(null_iv)); + ngtcp2_conn_set_retry_aead(conn, &retry_aead, &aead_ctx); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, buf, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(sizeof(token) == hd.token.len); + CU_ASSERT(0 == memcmp(token, hd.token.base, sizeof(token))); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_set_remote_transport_params(void) { + ngtcp2_conn *conn; + ngtcp2_transport_params params; + int rv; + ngtcp2_cid dcid; + uint8_t other_versions[2 * sizeof(uint32_t)]; + + dcid_init(&dcid); + + /* client: Successful case */ + setup_handshake_client(&conn); + + conn->negotiated_version = conn->client_chosen_version; + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); + + /* client: Wrong original_dcid */ + setup_handshake_client(&conn); + + conn->negotiated_version = conn->client_chosen_version; + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(NGTCP2_ERR_TRANSPORT_PARAM == rv); + + ngtcp2_conn_del(conn); + + /* client: Wrong initial_scid */ + setup_handshake_client(&conn); + + conn->negotiated_version = conn->client_chosen_version; + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.original_dcid = conn->rcid; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(NGTCP2_ERR_TRANSPORT_PARAM == rv); + + ngtcp2_conn_del(conn); + + /* client: Receiving retry_scid when retry is not attempted */ + setup_handshake_client(&conn); + + conn->negotiated_version = conn->client_chosen_version; + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + params.retry_scid_present = 1; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(NGTCP2_ERR_TRANSPORT_PARAM == rv); + + ngtcp2_conn_del(conn); + + /* client: Receiving retry_scid */ + setup_handshake_client(&conn); + + conn->flags |= NGTCP2_CONN_FLAG_RECV_RETRY; + conn->retry_scid = dcid; + conn->negotiated_version = conn->client_chosen_version; + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + params.retry_scid_present = 1; + params.retry_scid = dcid; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); + + /* client: Not receiving retry_scid when retry is attempted */ + setup_handshake_client(&conn); + + conn->flags |= NGTCP2_CONN_FLAG_RECV_RETRY; + conn->retry_scid = dcid; + conn->negotiated_version = conn->client_chosen_version; + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(NGTCP2_ERR_TRANSPORT_PARAM == rv); + + ngtcp2_conn_del(conn); + + /* client: Special handling for QUIC v1 regarding Version + Negotiation */ + setup_handshake_client(&conn); + + conn->local.settings.original_version = NGTCP2_PROTO_VER_V2_DRAFT; + conn->negotiated_version = conn->client_chosen_version; + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); + + /* client: No version_information after Version Negotiation */ + setup_handshake_client_version(&conn, NGTCP2_PROTO_VER_V2_DRAFT); + + conn->local.settings.original_version = NGTCP2_PROTO_VER_V1; + conn->negotiated_version = conn->client_chosen_version; + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE == rv); + + ngtcp2_conn_del(conn); + + /* client: other_versions includes the version that the client + initially attempted. */ + setup_handshake_client(&conn); + + conn->local.settings.original_version = NGTCP2_PROTO_VER_V2_DRAFT; + conn->negotiated_version = conn->client_chosen_version; + + ngtcp2_put_uint32be(other_versions, NGTCP2_PROTO_VER_V1); + ngtcp2_put_uint32be(other_versions + sizeof(uint32_t), + NGTCP2_PROTO_VER_V2_DRAFT); + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + params.version_info_present = 1; + params.version_info.chosen_version = conn->negotiated_version; + params.version_info.other_versions = other_versions; + params.version_info.other_versionslen = 2 * sizeof(uint32_t); + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE == rv); + + ngtcp2_conn_del(conn); + + /* client: client is unable to choose client chosen version from + server's other_versions and chosen version. */ + setup_handshake_client(&conn); + + conn->local.settings.original_version = NGTCP2_PROTO_VER_V2_DRAFT; + conn->negotiated_version = 0xff000000u; + + conn->local.settings.preferred_versions[0] = 0xff000001u; + conn->local.settings.preferred_versionslen = 1; + + ngtcp2_put_uint32be(conn->vneg.other_versions, NGTCP2_PROTO_VER_V1); + ngtcp2_put_uint32be(conn->vneg.other_versions + sizeof(uint32_t), + 0xff000000u); + + ngtcp2_put_uint32be(other_versions, 0xff000000u); + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + params.version_info_present = 1; + params.version_info.chosen_version = conn->negotiated_version; + params.version_info.other_versions = other_versions; + params.version_info.other_versionslen = 1; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE == rv); + + ngtcp2_conn_del(conn); + + /* client: client chooses version which differs from client chosen + version from server's other_versions and chosen version. */ + setup_handshake_client(&conn); + + conn->local.settings.original_version = NGTCP2_PROTO_VER_V2_DRAFT; + conn->negotiated_version = 0xff000000u; + + conn->local.settings.preferred_versions[0] = 0xff000000u; + conn->local.settings.preferred_versionslen = 1; + + ngtcp2_put_uint32be(conn->vneg.other_versions, NGTCP2_PROTO_VER_V1); + ngtcp2_put_uint32be(conn->vneg.other_versions + sizeof(uint32_t), + 0xff000000u); + + ngtcp2_put_uint32be(other_versions, 0xff000000u); + + memset(¶ms, 0, sizeof(params)); + params.active_connection_id_limit = NGTCP2_DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; + params.max_udp_payload_size = 1450; + params.initial_scid = conn->dcid.current.cid; + params.original_dcid = conn->rcid; + params.version_info_present = 1; + params.version_info.chosen_version = conn->negotiated_version; + params.version_info.other_versions = other_versions; + params.version_info.other_versionslen = 1; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(NGTCP2_ERR_VERSION_NEGOTIATION_FAILURE == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_write_connection_close(void) { + ngtcp2_conn *conn; + uint8_t buf[1200]; + ngtcp2_ssize spktlen, shdlen; + ngtcp2_pkt_hd hd; + const uint8_t *p; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_crypto_ctx crypto_ctx; + ngtcp2_connection_close_error ccerr; + + /* Client only Initial key */ + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 0); + + CU_ASSERT(spktlen > 0); + + ngtcp2_connection_close_error_set_transport_error(&ccerr, NGTCP2_NO_ERROR, + (const uint8_t *)"foo", 3); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, buf, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type); + CU_ASSERT(shdlen + (ngtcp2_ssize)hd.len == spktlen); + + ngtcp2_conn_del(conn); + + /* Client has Initial and Handshake keys */ + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 0); + + CU_ASSERT(spktlen > 0); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + ngtcp2_connection_close_error_set_transport_error_liberr(&ccerr, 0, NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, buf, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type); + CU_ASSERT(shdlen + (ngtcp2_ssize)hd.len == spktlen); + + ngtcp2_conn_del(conn); + + /* Client has all keys and has not confirmed handshake */ + setup_handshake_client(&conn); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx, + null_iv, sizeof(null_iv), &hp_ctx); + + conn->state = NGTCP2_CS_POST_HANDSHAKE; + + ngtcp2_connection_close_error_set_transport_error(&ccerr, NGTCP2_NO_ERROR, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + p = buf; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type); + + p += shdlen + (ngtcp2_ssize)hd.len; + spktlen -= shdlen + (ngtcp2_ssize)hd.len; + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, p, (size_t)spktlen, + conn->dcid.current.cid.datalen); + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_1RTT == hd.type); + + ngtcp2_conn_del(conn); + + /* Client has confirmed handshake */ + setup_default_client(&conn); + + ngtcp2_connection_close_error_set_transport_error(&ccerr, NGTCP2_NO_ERROR, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, buf, (size_t)spktlen, + conn->dcid.current.cid.datalen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_1RTT == hd.type); + + ngtcp2_conn_del(conn); + + /* Server has Initial and Handshake key */ + setup_handshake_server(&conn); + + conn->dcid.current.bytes_recv = NGTCP2_MAX_UDP_PAYLOAD_SIZE; + + init_initial_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_initial_crypto_ctx(conn, &crypto_ctx); + ngtcp2_conn_install_initial_key(conn, &aead_ctx, null_iv, &hp_ctx, &aead_ctx, + null_iv, &hp_ctx, sizeof(null_iv)); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + ngtcp2_connection_close_error_set_transport_error(&ccerr, NGTCP2_NO_ERROR, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + p = buf; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type); + + p += shdlen + (ngtcp2_ssize)hd.len; + spktlen -= shdlen + (ngtcp2_ssize)hd.len; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type); + CU_ASSERT(shdlen + (ngtcp2_ssize)hd.len == spktlen); + + ngtcp2_conn_del(conn); + + /* Server has all keys and has not confirmed handshake */ + setup_handshake_server(&conn); + + conn->dcid.current.bytes_recv = NGTCP2_MAX_UDP_PAYLOAD_SIZE; + + init_initial_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_initial_crypto_ctx(conn, &crypto_ctx); + ngtcp2_conn_install_initial_key(conn, &aead_ctx, null_iv, &hp_ctx, &aead_ctx, + null_iv, &hp_ctx, sizeof(null_iv)); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx, + null_iv, sizeof(null_iv), &hp_ctx); + + conn->state = NGTCP2_CS_POST_HANDSHAKE; + + ngtcp2_connection_close_error_set_transport_error(&ccerr, NGTCP2_NO_ERROR, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + p = buf; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type); + + p += shdlen + (ngtcp2_ssize)hd.len; + spktlen -= shdlen + (ngtcp2_ssize)hd.len; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type); + + p += shdlen + (ngtcp2_ssize)hd.len; + spktlen -= shdlen + (ngtcp2_ssize)hd.len; + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, p, (size_t)spktlen, + conn->dcid.current.cid.datalen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_1RTT == hd.type); + + ngtcp2_conn_del(conn); + + /* Server has confirmed handshake */ + setup_default_server(&conn); + + ngtcp2_connection_close_error_set_transport_error(&ccerr, NGTCP2_NO_ERROR, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, buf, (size_t)spktlen, + conn->dcid.current.cid.datalen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_1RTT == hd.type); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_write_application_close(void) { + ngtcp2_conn *conn; + uint8_t buf[1200]; + ngtcp2_ssize spktlen, shdlen; + ngtcp2_pkt_hd hd; + const uint8_t *p; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + uint64_t app_err_code = 0; + ngtcp2_crypto_ctx crypto_ctx; + ngtcp2_connection_close_error ccerr; + + /* Client only Initial key */ + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 0); + + CU_ASSERT(spktlen > 0); + + ngtcp2_connection_close_error_set_application_error( + &ccerr, app_err_code, (const uint8_t *)"foo", 3); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen >= NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, buf, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type); + CU_ASSERT(shdlen + (ngtcp2_ssize)hd.len == spktlen); + + ngtcp2_conn_del(conn); + + /* Client has Initial and Handshake keys */ + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), 0); + + CU_ASSERT(spktlen > 0); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + ngtcp2_connection_close_error_set_application_error(&ccerr, app_err_code, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, buf, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type); + CU_ASSERT(shdlen + (ngtcp2_ssize)hd.len == spktlen); + + ngtcp2_conn_del(conn); + + /* Client has all keys and has not confirmed handshake */ + setup_handshake_client(&conn); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx, + null_iv, sizeof(null_iv), &hp_ctx); + + conn->state = NGTCP2_CS_POST_HANDSHAKE; + + ngtcp2_connection_close_error_set_application_error(&ccerr, app_err_code, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + p = buf; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type); + + p += shdlen + (ngtcp2_ssize)hd.len; + spktlen -= shdlen + (ngtcp2_ssize)hd.len; + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, p, (size_t)spktlen, + conn->dcid.current.cid.datalen); + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_1RTT == hd.type); + + ngtcp2_conn_del(conn); + + /* Client has confirmed handshake */ + setup_default_client(&conn); + + ngtcp2_connection_close_error_set_application_error(&ccerr, app_err_code, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, buf, (size_t)spktlen, + conn->dcid.current.cid.datalen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_1RTT == hd.type); + + ngtcp2_conn_del(conn); + + /* Server has Initial and Handshake key */ + setup_handshake_server(&conn); + + conn->dcid.current.bytes_recv = NGTCP2_MAX_UDP_PAYLOAD_SIZE; + + init_initial_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_initial_crypto_ctx(conn, &crypto_ctx); + ngtcp2_conn_install_initial_key(conn, &aead_ctx, null_iv, &hp_ctx, &aead_ctx, + null_iv, &hp_ctx, sizeof(null_iv)); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + ngtcp2_connection_close_error_set_application_error(&ccerr, app_err_code, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + p = buf; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type); + + p += shdlen + (ngtcp2_ssize)hd.len; + spktlen -= shdlen + (ngtcp2_ssize)hd.len; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type); + CU_ASSERT(shdlen + (ngtcp2_ssize)hd.len == spktlen); + + ngtcp2_conn_del(conn); + + /* Server has all keys and has not confirmed handshake */ + setup_handshake_server(&conn); + + conn->dcid.current.bytes_recv = NGTCP2_MAX_UDP_PAYLOAD_SIZE; + + init_initial_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_initial_crypto_ctx(conn, &crypto_ctx); + ngtcp2_conn_install_initial_key(conn, &aead_ctx, null_iv, &hp_ctx, &aead_ctx, + null_iv, &hp_ctx, sizeof(null_iv)); + + init_crypto_ctx(&crypto_ctx); + + ngtcp2_conn_set_crypto_ctx(conn, &crypto_ctx); + conn->negotiated_version = conn->client_chosen_version; + ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), &aead_ctx, + null_iv, sizeof(null_iv), &hp_ctx); + + ngtcp2_connection_close_error_set_application_error(&ccerr, app_err_code, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + p = buf; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type); + + p += shdlen + (ngtcp2_ssize)hd.len; + spktlen -= shdlen + (ngtcp2_ssize)hd.len; + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, p, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_HANDSHAKE == hd.type); + + p += shdlen + (ngtcp2_ssize)hd.len; + spktlen -= shdlen + (ngtcp2_ssize)hd.len; + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, p, (size_t)spktlen, + conn->dcid.current.cid.datalen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_1RTT == hd.type); + + ngtcp2_conn_del(conn); + + /* Server has confirmed handshake */ + setup_default_server(&conn); + + ngtcp2_connection_close_error_set_application_error(&ccerr, app_err_code, + NULL, 0); + + spktlen = ngtcp2_conn_write_connection_close(conn, NULL, NULL, buf, + sizeof(buf), &ccerr, 0); + + CU_ASSERT(spktlen > 0); + CU_ASSERT(spktlen < NGTCP2_MAX_UDP_PAYLOAD_SIZE); + + shdlen = ngtcp2_pkt_decode_hd_short(&hd, buf, (size_t)spktlen, + conn->dcid.current.cid.datalen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(NGTCP2_PKT_1RTT == hd.type); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_rtb_reclaim_on_pto(void) { + ngtcp2_conn *conn; + int rv; + int64_t stream_id; + uint8_t buf[2048]; + ngtcp2_ssize nwrite; + ngtcp2_ssize spktlen; + size_t i; + size_t num_reclaim_pkt; + ngtcp2_rtb_entry *ent; + ngtcp2_ksl_it it; + + setup_default_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + for (i = 0; i < 5; ++i) { + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 1); + + CU_ASSERT(0 < spktlen); + } + + CU_ASSERT(5 == ngtcp2_ksl_len(&conn->pktns.rtb.ents)); + + rv = ngtcp2_conn_on_loss_detection_timer(conn, 3 * NGTCP2_SECONDS); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), + 3 * NGTCP2_SECONDS); + + CU_ASSERT(spktlen > 0); + + it = ngtcp2_ksl_begin(&conn->pktns.rtb.ents); + num_reclaim_pkt = 0; + for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { + ent = ngtcp2_ksl_it_get(&it); + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED) { + ++num_reclaim_pkt; + } + } + + CU_ASSERT(1 == num_reclaim_pkt); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_rtb_reclaim_on_pto_datagram(void) { + ngtcp2_conn *conn; + int rv; + int64_t stream_id; + uint8_t buf[2048]; + ngtcp2_ssize nwrite; + ngtcp2_ssize spktlen; + size_t num_reclaim_pkt; + ngtcp2_rtb_entry *ent; + ngtcp2_ksl_it it; + ngtcp2_vec datav; + int accepted; + ngtcp2_frame_chain *frc; + + /* DATAGRAM frame must not be reclaimed on PTO */ + setup_default_client(&conn); + + conn->callbacks.ack_datagram = ack_datagram; + conn->remote.transport_params->max_datagram_frame_size = 65535; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 1); + + CU_ASSERT(0 < spktlen); + + datav.base = null_data; + datav.len = 10; + + spktlen = ngtcp2_conn_writev_datagram( + conn, NULL, NULL, buf, sizeof(buf), &accepted, + NGTCP2_WRITE_DATAGRAM_FLAG_NONE, 1000000007, &datav, 1, 1); + + CU_ASSERT(accepted); + CU_ASSERT(0 < spktlen); + CU_ASSERT(2 == ngtcp2_ksl_len(&conn->pktns.rtb.ents)); + + rv = ngtcp2_conn_on_loss_detection_timer(conn, 3 * NGTCP2_SECONDS); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), + 3 * NGTCP2_SECONDS); + + CU_ASSERT(spktlen > 0); + + it = ngtcp2_ksl_begin(&conn->pktns.rtb.ents); + num_reclaim_pkt = 0; + for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { + ent = ngtcp2_ksl_it_get(&it); + if (ent->flags & NGTCP2_RTB_ENTRY_FLAG_PTO_RECLAIMED) { + ++num_reclaim_pkt; + for (frc = ent->frc; frc; frc = frc->next) { + CU_ASSERT(NGTCP2_FRAME_DATAGRAM != frc->fr.type); + } + } + } + + CU_ASSERT(1 == num_reclaim_pkt); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_validate_ecn(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_ssize spktlen; + ngtcp2_pkt_info pi; + size_t pktlen; + int rv; + ngtcp2_frame fr; + int64_t stream_id; + ngtcp2_ssize nwrite; + size_t i; + ngtcp2_tstamp t = 0; + + setup_default_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, &pi, buf, sizeof(buf), 1); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_ECT_0 == pi.ecn); + CU_ASSERT(NGTCP2_ECN_STATE_TESTING == conn->tx.ecn.state); + CU_ASSERT(1 == conn->tx.ecn.validation_start_ts); + CU_ASSERT(0 == conn->pktns.tx.ecn.start_pkt_num); + + fr.type = NGTCP2_FRAME_ACK_ECN; + fr.ack.largest_ack = 0; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + fr.ack.ecn.ect0 = 1; + fr.ack.ecn.ect1 = 0; + fr.ack.ecn.ce = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_ECN_STATE_CAPABLE == conn->tx.ecn.state); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, &pi, buf, sizeof(buf), &nwrite, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 1024, 2); + + CU_ASSERT(0 < spktlen); + + /* Receiving ACK frame containing less ECN counts fails + validation */ + fr.type = NGTCP2_FRAME_ACK_ECN; + fr.ack.largest_ack = 1; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + fr.ack.ecn.ect0 = 0; + fr.ack.ecn.ect1 = 0; + fr.ack.ecn.ce = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 1, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 3); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_ECN_STATE_FAILED == conn->tx.ecn.state); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, &pi, buf, sizeof(buf), &nwrite, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 1024, 2); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_NOT_ECT == pi.ecn); + + ngtcp2_conn_del(conn); + + /* Receiving ACK frame without ECN counts invalidates ECN + capability */ + setup_default_server(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, &pi, buf, sizeof(buf), 1); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_ECT_0 == pi.ecn); + CU_ASSERT(NGTCP2_ECN_STATE_TESTING == conn->tx.ecn.state); + CU_ASSERT(1 == conn->tx.ecn.validation_start_ts); + CU_ASSERT(0 == conn->pktns.tx.ecn.start_pkt_num); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = 0; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_ECN_STATE_FAILED == conn->tx.ecn.state); + + ngtcp2_conn_del(conn); + + /* CE counts must be considered */ + setup_default_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + for (i = 0; i < 2; ++i) { + spktlen = ngtcp2_conn_write_stream(conn, NULL, &pi, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 1024, 2); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_ECT_0 == pi.ecn); + } + + CU_ASSERT(NGTCP2_ECN_STATE_TESTING == conn->tx.ecn.state); + CU_ASSERT(2 == conn->tx.ecn.validation_start_ts); + CU_ASSERT(0 == conn->pktns.tx.ecn.start_pkt_num); + + fr.type = NGTCP2_FRAME_ACK_ECN; + fr.ack.largest_ack = 1; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 1; + fr.ack.rangecnt = 0; + fr.ack.ecn.ect0 = 1; + fr.ack.ecn.ect1 = 0; + fr.ack.ecn.ce = 1; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_ECN_STATE_CAPABLE == conn->tx.ecn.state); + CU_ASSERT(0 == ngtcp2_ksl_len(&conn->pktns.rtb.ents)); + + ngtcp2_conn_del(conn); + + /* If increments of ECN counts is less than the number of + acknowledged ECN entries, ECN validation fails. */ + setup_default_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, &pi, buf, sizeof(buf), 1); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_ECT_0 == pi.ecn); + CU_ASSERT(NGTCP2_ECN_STATE_TESTING == conn->tx.ecn.state); + CU_ASSERT(1 == conn->tx.ecn.validation_start_ts); + CU_ASSERT(0 == conn->pktns.tx.ecn.start_pkt_num); + + fr.type = NGTCP2_FRAME_ACK_ECN; + fr.ack.largest_ack = 0; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + fr.ack.ecn.ect0 = 0; + fr.ack.ecn.ect1 = 1; + fr.ack.ecn.ce = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_ECN_STATE_FAILED == conn->tx.ecn.state); + + ngtcp2_conn_del(conn); + + /* If ECT count is larger than the number of ECT marked packet, ECN + validation fails. */ + setup_default_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, &pi, buf, sizeof(buf), 1); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_ECT_0 == pi.ecn); + CU_ASSERT(NGTCP2_ECN_STATE_TESTING == conn->tx.ecn.state); + CU_ASSERT(1 == conn->tx.ecn.validation_start_ts); + CU_ASSERT(0 == conn->pktns.tx.ecn.start_pkt_num); + + fr.type = NGTCP2_FRAME_ACK_ECN; + fr.ack.largest_ack = 0; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + fr.ack.ecn.ect0 = 2; + fr.ack.ecn.ect1 = 0; + fr.ack.ecn.ce = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, 2); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_ECN_STATE_FAILED == conn->tx.ecn.state); + + ngtcp2_conn_del(conn); + + /* ECN validation fails if all ECN marked packets are lost */ + setup_default_client(&conn); + + t = 0; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + for (i = 0; i < NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS; ++i) { + spktlen = ngtcp2_conn_write_stream(conn, NULL, &pi, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 25, t); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_ECT_0 == pi.ecn); + } + + CU_ASSERT(NGTCP2_ECN_STATE_UNKNOWN == conn->tx.ecn.state); + CU_ASSERT(NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS == conn->tx.ecn.dgram_sent); + + t += NGTCP2_MILLISECONDS; + + spktlen = ngtcp2_conn_write_stream(conn, NULL, &pi, buf, sizeof(buf), &nwrite, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 25, t); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_NOT_ECT == pi.ecn); + CU_ASSERT(NGTCP2_ECN_STATE_UNKNOWN == conn->tx.ecn.state); + CU_ASSERT(0 == conn->tx.ecn.validation_start_ts); + CU_ASSERT(0 == conn->pktns.tx.ecn.start_pkt_num); + CU_ASSERT(NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS == conn->tx.ecn.dgram_sent); + CU_ASSERT(0 == conn->pktns.tx.ecn.validation_pkt_lost); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + t += NGTCP2_MILLISECONDS; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, t); + + CU_ASSERT(0 == rv); + + CU_ASSERT(NGTCP2_ECN_STATE_FAILED == conn->tx.ecn.state); + CU_ASSERT(NGTCP2_ECN_MAX_NUM_VALIDATION_PKTS == + conn->pktns.tx.ecn.validation_pkt_lost); + + ngtcp2_conn_del(conn); + + /* ECN validation fails if all ECN marked packets sent in last 3 * + RTT are lost */ + setup_default_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + for (i = 0; i < 2; ++i) { + spktlen = ngtcp2_conn_write_stream(conn, NULL, &pi, buf, sizeof(buf), + &nwrite, NGTCP2_WRITE_STREAM_FLAG_NONE, + stream_id, null_data, 25, 0); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_ECT_0 == pi.ecn); + } + + CU_ASSERT(NGTCP2_ECN_STATE_TESTING == conn->tx.ecn.state); + CU_ASSERT(2 == conn->tx.ecn.dgram_sent); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, &pi, buf, sizeof(buf), &nwrite, + NGTCP2_WRITE_STREAM_FLAG_NONE, stream_id, + null_data, 25, 3 * NGTCP2_SECONDS); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NGTCP2_ECN_NOT_ECT == pi.ecn); + CU_ASSERT(NGTCP2_ECN_STATE_UNKNOWN == conn->tx.ecn.state); + CU_ASSERT(0 == conn->tx.ecn.validation_start_ts); + CU_ASSERT(0 == conn->pktns.tx.ecn.start_pkt_num); + CU_ASSERT(2 == conn->pktns.tx.ecn.validation_pkt_sent); + CU_ASSERT(0 == conn->pktns.tx.ecn.validation_pkt_lost); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = 2; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, 0, &fr, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, + 4 * NGTCP2_SECONDS); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_ECN_STATE_FAILED == conn->tx.ecn.state); + CU_ASSERT(2 == conn->pktns.tx.ecn.validation_pkt_lost); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_path_validation(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_ssize spktlen; + ngtcp2_tstamp t = 0; + int64_t pkt_num = 0; + ngtcp2_frame frs[4]; + int rv; + ngtcp2_path_storage rpath, wpath; + ngtcp2_pv_entry *ent; + + /* server starts path validation in NAT rebinding scenario. */ + setup_default_server(&conn); + + /* This will send NEW_CONNECTION_ID frames */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + frs[0].type = NGTCP2_FRAME_PING; + + /* Just change remote port */ + path_init(&rpath, 0, 0, 0, 1); + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &rpath.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL != conn->pv); + CU_ASSERT(0 == conn->pv->dcid.seq); + CU_ASSERT(ngtcp2_path_eq(&conn->pv->dcid.ps.path, &rpath.path)); + + ngtcp2_path_storage_zero(&wpath); + spktlen = + ngtcp2_conn_write_pkt(conn, &wpath.path, NULL, buf, sizeof(buf), ++t); + + /* Server has not received enough bytes to pad probing packet. */ + CU_ASSERT(1200 > spktlen); + CU_ASSERT(ngtcp2_path_eq(&rpath.path, &wpath.path)); + CU_ASSERT(1 == ngtcp2_ringbuf_len(&conn->pv->ents.rb)); + + ent = ngtcp2_ringbuf_get(&conn->pv->ents.rb, 0); + + CU_ASSERT(ent->flags & NGTCP2_PV_ENTRY_FLAG_UNDERSIZED); + CU_ASSERT(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE); + CU_ASSERT(!(conn->pv->flags & NGTCP2_PV_FLAG_MTU_PROBE)); + + frs[0].type = NGTCP2_FRAME_PATH_RESPONSE; + memcpy(frs[0].path_response.data, ent->data, sizeof(ent->data)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &rpath.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + /* Start another path validation to probe least MTU */ + CU_ASSERT(NULL != conn->pv); + CU_ASSERT(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE); + CU_ASSERT(conn->pv->flags & NGTCP2_PV_FLAG_MTU_PROBE); + + ngtcp2_path_storage_zero(&wpath); + spktlen = + ngtcp2_conn_write_pkt(conn, &wpath.path, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(1200 <= spktlen); + CU_ASSERT(ngtcp2_path_eq(&rpath.path, &wpath.path)); + CU_ASSERT(1 == ngtcp2_ringbuf_len(&conn->pv->ents.rb)); + + ent = ngtcp2_ringbuf_get(&conn->pv->ents.rb, 0); + frs[0].type = NGTCP2_FRAME_PATH_RESPONSE; + memcpy(frs[0].path_response.data, ent->data, sizeof(ent->data)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 1, + conn->pktns.crypto.rx.ckm); + rv = ngtcp2_conn_read_pkt(conn, &rpath.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + /* Now perform another validation to old path */ + CU_ASSERT(NULL != conn->pv); + CU_ASSERT(!(conn->pv->flags & NGTCP2_PV_FLAG_FALLBACK_ON_FAILURE)); + CU_ASSERT(!(conn->pv->flags & NGTCP2_PV_FLAG_MTU_PROBE)); + CU_ASSERT(conn->pv->flags & NGTCP2_PV_FLAG_DONT_CARE); + + ngtcp2_path_storage_zero(&wpath); + spktlen = + ngtcp2_conn_write_pkt(conn, &wpath.path, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(1200 <= spktlen); + CU_ASSERT(ngtcp2_path_eq(&null_path.path, &wpath.path)); + CU_ASSERT(1 == ngtcp2_ringbuf_len(&conn->pv->ents.rb)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_early_data_sync_stream_data_limit(void) { + ngtcp2_conn *conn; + uint8_t buf[1024]; + ngtcp2_ssize spktlen; + ngtcp2_ssize datalen; + int64_t bidi_stream_id, uni_stream_id; + int rv; + ngtcp2_frame fr; + size_t pktlen; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_transport_params params; + ngtcp2_strm *strm; + ngtcp2_tstamp t = 0; + + setup_early_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &bidi_stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &datalen, NGTCP2_WRITE_STREAM_FLAG_FIN, + bidi_stream_id, null_data, 1024, ++t); + + CU_ASSERT((ngtcp2_ssize)sizeof(buf) == spktlen); + CU_ASSERT(670 == datalen); + + rv = ngtcp2_conn_open_uni_stream(conn, &uni_stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &datalen, NGTCP2_WRITE_STREAM_FLAG_FIN, + uni_stream_id, null_data, 1024, ++t); + + CU_ASSERT((ngtcp2_ssize)sizeof(buf) == spktlen); + CU_ASSERT(958); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 198; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), 0, NGTCP2_PROTO_VER_V1, + NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_install_rx_key(conn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + + CU_ASSERT(0 == rv); + + memset(¶ms, 0, sizeof(params)); + ngtcp2_cid_init(¶ms.initial_scid, conn->dcid.current.cid.data, + conn->dcid.current.cid.datalen); + ngtcp2_cid_init(¶ms.original_dcid, conn->rcid.data, conn->rcid.datalen); + params.max_udp_payload_size = 1200; + params.initial_max_stream_data_bidi_local = + conn->early.transport_params.initial_max_stream_data_bidi_local; + params.initial_max_stream_data_bidi_remote = 640 * 1024; + params.initial_max_stream_data_uni = 320 * 1024; + params.initial_max_data = conn->early.transport_params.initial_max_data; + params.initial_max_streams_bidi = + conn->early.transport_params.initial_max_streams_bidi; + params.initial_max_streams_uni = + conn->early.transport_params.initial_max_streams_uni; + params.active_connection_id_limit = + conn->early.transport_params.active_connection_id_limit; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_handshake_completed(conn); + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 < spktlen); + + strm = ngtcp2_conn_find_stream(conn, bidi_stream_id); + + CU_ASSERT(params.initial_max_stream_data_bidi_remote == strm->tx.max_offset); + + strm = ngtcp2_conn_find_stream(conn, uni_stream_id); + + CU_ASSERT(params.initial_max_stream_data_uni == strm->tx.max_offset); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_early_data_rejected(void) { + ngtcp2_conn *conn; + uint8_t buf[1024]; + ngtcp2_ssize spktlen; + ngtcp2_ssize datalen; + int64_t bidi_stream_id, uni_stream_id; + int rv; + ngtcp2_frame fr; + size_t pktlen; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_transport_params params; + ngtcp2_tstamp t = 0; + + setup_early_client(&conn); + + rv = ngtcp2_conn_open_bidi_stream(conn, &bidi_stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &datalen, NGTCP2_WRITE_STREAM_FLAG_FIN, + bidi_stream_id, null_data, 1024, ++t); + + CU_ASSERT((ngtcp2_ssize)sizeof(buf) == spktlen); + CU_ASSERT(670 == datalen); + + rv = ngtcp2_conn_open_uni_stream(conn, &uni_stream_id, NULL); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_extend_max_offset(conn, 1000); + ngtcp2_conn_extend_max_streams_bidi(conn, 7); + ngtcp2_conn_extend_max_streams_uni(conn, 5); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), + &datalen, NGTCP2_WRITE_STREAM_FLAG_FIN, + uni_stream_id, null_data, 300, ++t); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(0 < conn->tx.offset); + CU_ASSERT(conn->local.transport_params.initial_max_data + 1000 == + conn->rx.unsent_max_offset); + CU_ASSERT(conn->local.transport_params.initial_max_streams_bidi + 7 == + conn->remote.bidi.unsent_max_streams); + CU_ASSERT(conn->local.transport_params.initial_max_streams_bidi + 7 == + conn->remote.bidi.max_streams); + CU_ASSERT(conn->local.transport_params.initial_max_streams_uni + 5 == + conn->remote.uni.unsent_max_streams); + CU_ASSERT(conn->local.transport_params.initial_max_streams_uni + 5 == + conn->remote.uni.max_streams); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 198; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), 0, NGTCP2_PROTO_VER_V1, + NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_install_rx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_install_tx_handshake_key(conn, &aead_ctx, null_iv, + sizeof(null_iv), &hp_ctx); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_install_rx_key(conn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + + CU_ASSERT(0 == rv); + + /* Stream limits in transport parameters can be reduced if early + data is rejected. */ + memset(¶ms, 0, sizeof(params)); + ngtcp2_cid_init(¶ms.initial_scid, conn->dcid.current.cid.data, + conn->dcid.current.cid.datalen); + ngtcp2_cid_init(¶ms.original_dcid, conn->rcid.data, conn->rcid.datalen); + params.max_udp_payload_size = 1200; + params.initial_max_stream_data_bidi_local = + conn->early.transport_params.initial_max_stream_data_bidi_local; + params.initial_max_stream_data_bidi_remote = + conn->early.transport_params.initial_max_stream_data_bidi_remote / 2; + params.initial_max_stream_data_uni = 0; + params.initial_max_data = conn->early.transport_params.initial_max_data; + params.initial_max_streams_bidi = + conn->early.transport_params.initial_max_streams_bidi; + params.initial_max_streams_uni = + conn->early.transport_params.initial_max_streams_uni; + params.active_connection_id_limit = + conn->early.transport_params.active_connection_id_limit; + + rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_handshake_completed(conn); + ngtcp2_conn_early_data_rejected(conn); + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, bidi_stream_id)); + CU_ASSERT(NULL == ngtcp2_conn_find_stream(conn, uni_stream_id)); + CU_ASSERT(0 == conn->tx.offset); + CU_ASSERT(conn->local.transport_params.initial_max_data == + conn->rx.max_offset); + CU_ASSERT(conn->local.transport_params.initial_max_data == + conn->rx.unsent_max_offset); + CU_ASSERT(conn->local.transport_params.initial_max_streams_bidi == + conn->remote.bidi.max_streams); + CU_ASSERT(conn->local.transport_params.initial_max_streams_bidi == + conn->remote.bidi.unsent_max_streams); + CU_ASSERT(conn->local.transport_params.initial_max_streams_uni == + conn->remote.uni.max_streams); + CU_ASSERT(conn->local.transport_params.initial_max_streams_uni == + conn->remote.uni.unsent_max_streams); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_keep_alive(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_ssize spktlen; + ngtcp2_pkt_info pi; + ngtcp2_tstamp t = 0; + int rv; + + setup_default_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, &pi, buf, sizeof(buf), ++t); + + CU_ASSERT(0 < spktlen); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, &pi, buf, sizeof(buf), ++t); + + CU_ASSERT(0 == spktlen); + + ngtcp2_conn_set_keep_alive_timeout(conn, 10 * NGTCP2_SECONDS); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, &pi, buf, sizeof(buf), t); + + CU_ASSERT(0 == spktlen); + + t += 10 * NGTCP2_SECONDS; + + rv = ngtcp2_conn_handle_expiry(conn, t); + + CU_ASSERT(0 == rv); + CU_ASSERT(conn->flags & NGTCP2_CONN_FLAG_KEEP_ALIVE_CANCELLED); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, &pi, buf, sizeof(buf), t); + + CU_ASSERT(0 < spktlen); + CU_ASSERT(t == conn->keep_alive.last_ts); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_retire_stale_bound_dcid(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + size_t pktlen; + ngtcp2_tstamp t = 0; + ngtcp2_tstamp expiry; + int64_t pkt_num = 0; + ngtcp2_frame fr; + int rv; + ngtcp2_cid cid; + const uint8_t raw_cid[] = {0x0f, 0x00, 0x00, 0x00}; + const uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN] = {0xff}; + const uint8_t data[] = {0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8}; + + ngtcp2_cid_init(&cid, raw_cid, sizeof(raw_cid)); + + setup_default_server(&conn); + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.new_connection_id.seq = 1; + fr.new_connection_id.retire_prior_to = 0; + fr.new_connection_id.cid = cid; + memcpy(fr.new_connection_id.stateless_reset_token, token, sizeof(token)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_PATH_CHALLENGE; + memcpy(fr.path_challenge.data, data, sizeof(fr.path_challenge.data)); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &new_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_ringbuf_len(&conn->rx.path_challenge.rb) > 0); + CU_ASSERT(ngtcp2_ringbuf_len(&conn->dcid.bound.rb) > 0); + + expiry = ngtcp2_conn_get_expiry(conn); + + CU_ASSERT(UINT64_MAX != expiry); + + t += 3 * ngtcp2_conn_get_pto(conn); + + rv = ngtcp2_conn_handle_expiry(conn, t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ngtcp2_ringbuf_len(&conn->dcid.bound.rb)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_get_scid(void) { + ngtcp2_conn *conn; + ngtcp2_settings settings; + ngtcp2_transport_params params; + ngtcp2_cid dcid, scid; + ngtcp2_callbacks cb; + const uint8_t raw_cid[] = {0x0f, 0x00, 0x00, 0x00}; + ngtcp2_cid scids[16]; + + dcid_init(&dcid); + dcid_init(&scid); + + server_default_callbacks(&cb); + server_default_settings(&settings); + + /* Without preferred address */ + server_default_transport_params(¶ms); + + ngtcp2_conn_server_new(&conn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + /* mem = */ NULL, NULL); + + CU_ASSERT(1 == ngtcp2_conn_get_num_scid(conn)); + + ngtcp2_conn_get_scid(conn, scids); + + CU_ASSERT(ngtcp2_cid_eq(&scid, &scids[0])); + + ngtcp2_conn_del(conn); + + /* With preferred address */ + server_default_transport_params(¶ms); + params.preferred_address_present = 1; + ngtcp2_cid_init(¶ms.preferred_address.cid, raw_cid, sizeof(raw_cid)); + + ngtcp2_conn_server_new(&conn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + /* mem = */ NULL, NULL); + + CU_ASSERT(2 == ngtcp2_conn_get_num_scid(conn)); + + ngtcp2_conn_get_scid(conn, scids); + + CU_ASSERT(ngtcp2_cid_eq(&scid, &scids[0])); + CU_ASSERT(ngtcp2_cid_eq(¶ms.preferred_address.cid, &scids[1])); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_stream_close(void) { + ngtcp2_conn *conn; + int rv; + uint8_t buf[2048]; + ngtcp2_frame frs[2]; + size_t pktlen; + int64_t pkt_num = 0; + my_user_data ud; + ngtcp2_strm *strm; + ngtcp2_tstamp t = 0; + ngtcp2_ssize spktlen; + int64_t stream_id; + + /* Receive RESET_STREAM and STOP_SENDING from client */ + setup_default_server(&conn); + conn->callbacks.stream_close = stream_close; + conn->user_data = &ud; + + open_stream(conn, 0); + + frs[0].type = NGTCP2_FRAME_RESET_STREAM; + frs[0].reset_stream.stream_id = 0; + frs[0].reset_stream.app_error_code = NGTCP2_APP_ERR01; + frs[0].reset_stream.final_size = 999; + + frs[1].type = NGTCP2_FRAME_STOP_SENDING; + frs[1].stop_sending.stream_id = 0; + frs[1].stop_sending.app_error_code = NGTCP2_APP_ERR02; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.tx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + strm = ngtcp2_conn_find_stream(conn, 0); + + CU_ASSERT(NGTCP2_APP_ERR01 == strm->app_error_code); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT((size_t)spktlen < sizeof(buf)); + + frs[0].type = NGTCP2_FRAME_ACK; + frs[0].ack.largest_ack = 0; + frs[0].ack.ack_delay = 0; + frs[0].ack.first_ack_range = 0; + frs[0].ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 1, + conn->pktns.crypto.tx.ckm); + + ud.stream_close.flags = NGTCP2_STREAM_CLOSE_FLAG_NONE; + ud.stream_close.stream_id = -1; + ud.stream_close.app_error_code = 0; + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + CU_ASSERT(NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET & + ud.stream_close.flags); + CU_ASSERT(0 == ud.stream_close.stream_id); + CU_ASSERT(NGTCP2_APP_ERR01 == ud.stream_close.app_error_code); + + ngtcp2_conn_del(conn); + + /* Client sends STOP_SENDING and then STREAM and fin */ + pkt_num = 0; + + setup_default_server(&conn); + conn->callbacks.stream_close = stream_close; + conn->callbacks.recv_stream_data = recv_stream_data; + conn->user_data = &ud; + + frs[0].type = NGTCP2_FRAME_STOP_SENDING; + frs[0].stop_sending.stream_id = 0; + frs[0].stop_sending.app_error_code = NGTCP2_APP_ERR01; + + frs[1].type = NGTCP2_FRAME_STREAM; + frs[1].stream.flags = 0; + frs[1].stream.fin = 1; + frs[1].stream.stream_id = 0; + frs[1].stream.offset = 0; + frs[1].stream.datacnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.tx.ckm); + + ud.stream_data.stream_id = -1; + ud.stream_data.flags = NGTCP2_STREAM_DATA_FLAG_NONE; + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ud.stream_data.stream_id); + CU_ASSERT((ud.stream_data.flags & NGTCP2_STREAM_DATA_FLAG_FIN) != 0); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + CU_ASSERT((size_t)spktlen < sizeof(buf)); + + frs[0].type = NGTCP2_FRAME_ACK; + frs[0].ack.largest_ack = 0; + frs[0].ack.ack_delay = 0; + frs[0].ack.first_ack_range = 0; + frs[0].ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 1, + conn->pktns.crypto.tx.ckm); + + ud.stream_close.flags = NGTCP2_STREAM_CLOSE_FLAG_NONE; + ud.stream_close.stream_id = -1; + ud.stream_close.app_error_code = 0; + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + CU_ASSERT(NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET & + ud.stream_close.flags); + CU_ASSERT(0 == ud.stream_close.stream_id); + CU_ASSERT(NGTCP2_APP_ERR01 == ud.stream_close.app_error_code); + + ngtcp2_conn_del(conn); + + /* Client calls ngtcp2_conn_shutdown_stream, and before sending + STOP_SENDING, it receives STREAM with fin bit set. */ + pkt_num = 0; + + setup_default_client(&conn); + conn->callbacks.stream_close = stream_close; + conn->user_data = &ud; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_FIN, stream_id, + null_data, 1, ++t); + + CU_ASSERT(spktlen > 0); + + frs[0].type = NGTCP2_FRAME_ACK; + frs[0].ack.largest_ack = conn->pktns.tx.last_pkt_num; + frs[0].ack.ack_delay = 0; + frs[0].ack.first_ack_range = 0; + frs[0].ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 1, + conn->pktns.crypto.tx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_shutdown_stream(conn, stream_id, NGTCP2_APP_ERR01); + + CU_ASSERT(0 == rv); + + frs[0].type = NGTCP2_FRAME_STREAM; + frs[0].stream.flags = 0; + frs[0].stream.fin = 1; + frs[0].stream.stream_id = stream_id; + frs[0].stream.offset = 0; + frs[0].stream.datacnt = 1; + frs[0].stream.data[0].len = 97; + frs[0].stream.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 1, + conn->pktns.crypto.tx.ckm); + + ud.stream_close.flags = NGTCP2_STREAM_CLOSE_FLAG_NONE; + ud.stream_close.stream_id = -1; + ud.stream_close.app_error_code = 0; + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET & + ud.stream_close.flags); + CU_ASSERT(stream_id == ud.stream_close.stream_id); + CU_ASSERT(NGTCP2_APP_ERR01 == ud.stream_close.app_error_code); + + ngtcp2_conn_del(conn); + + /* Client sends STREAM fin and then RESET_STREAM. It receives ACK + for the STREAM frame, then response fin. No ACK for + RESET_STREAM. */ + pkt_num = 0; + + setup_default_client(&conn); + conn->callbacks.stream_close = stream_close; + conn->user_data = &ud; + + rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); + + CU_ASSERT(0 == rv); + + spktlen = ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_FIN, stream_id, + null_data, 1, ++t); + + CU_ASSERT(spktlen > 0); + + rv = ngtcp2_conn_shutdown_stream_write(conn, stream_id, NGTCP2_APP_ERR01); + + CU_ASSERT(0 == rv); + + frs[0].type = NGTCP2_FRAME_STREAM; + frs[0].stream.flags = 0; + frs[0].stream.fin = 1; + frs[0].stream.stream_id = stream_id; + frs[0].stream.offset = 0; + frs[0].stream.datacnt = 0; + + frs[1].type = NGTCP2_FRAME_ACK; + frs[1].ack.largest_ack = conn->pktns.tx.last_pkt_num; + frs[1].ack.ack_delay = 0; + frs[1].ack.first_ack_range = 0; + frs[1].ack.rangecnt = 0; + + spktlen = + ngtcp2_conn_write_stream(conn, NULL, NULL, buf, sizeof(buf), NULL, + NGTCP2_WRITE_STREAM_FLAG_NONE, -1, NULL, 0, ++t); + + CU_ASSERT(spktlen > 0); + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, 2, + conn->pktns.crypto.tx.ckm); + + ud.stream_close.flags = NGTCP2_STREAM_CLOSE_FLAG_NONE; + ud.stream_close.stream_id = -1; + ud.stream_close.app_error_code = 0; + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET & + ud.stream_close.flags); + CU_ASSERT(stream_id == ud.stream_close.stream_id); + CU_ASSERT(NGTCP2_APP_ERR01 == ud.stream_close.app_error_code); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_buffer_pkt(void) { + ngtcp2_conn *conn; + int rv; + uint8_t buf[2048]; + ngtcp2_frame fr; + ngtcp2_frame frs[2]; + size_t pktlen, in_pktlen; + int64_t pkt_num = 0; + ngtcp2_tstamp t = 0; + ngtcp2_ssize spktlen; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + ngtcp2_ksl_it it; + ngtcp2_pkt_chain *pc; + + /* Server should buffer Short packet if it does not complete + handshake even if it has application tx key. */ + setup_handshake_server(&conn); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1193; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), pkt_num++, + NGTCP2_PROTO_VER_V1, NULL, 0, &fr, 1, &null_ckm); + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_conn_install_tx_key(conn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + + assert(0 == rv); + + rv = ngtcp2_conn_install_rx_key(conn, null_secret, sizeof(null_secret), + &aead_ctx, null_iv, sizeof(null_iv), &hp_ctx); + + assert(0 == rv); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_PING; + + in_pktlen = write_initial_pkt( + buf, sizeof(buf), &conn->oscid, ngtcp2_conn_get_dcid(conn), pkt_num++, + NGTCP2_PROTO_VER_V1, NULL, 0, &fr, 1, &null_ckm); + + frs[0].type = NGTCP2_FRAME_PING; + frs[1].type = NGTCP2_FRAME_PADDING; + frs[1].padding.len = 1200; + + pktlen = write_pkt(buf + in_pktlen, sizeof(buf) - in_pktlen, &conn->oscid, + pkt_num++, frs, 2, &null_ckm); + + CU_ASSERT(!conn->pktns.rx.buffed_pkts); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, + in_pktlen + pktlen, ++t); + + CU_ASSERT(0 == rv); + + pc = conn->pktns.rx.buffed_pkts; + + CU_ASSERT(pktlen == pc->pktlen); + CU_ASSERT(in_pktlen + pktlen == pc->dgramlen); + + it = ngtcp2_acktr_get(&conn->pktns.acktr); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_handshake_timeout(void) { + ngtcp2_conn *conn; + int rv; + + /* handshake has just timed out */ + setup_handshake_server(&conn); + + rv = ngtcp2_conn_handle_expiry(conn, + conn->local.settings.initial_ts + + conn->local.settings.handshake_timeout); + + CU_ASSERT(NGTCP2_ERR_HANDSHAKE_TIMEOUT == rv); + + ngtcp2_conn_del(conn); + + /* handshake is still in progress */ + setup_handshake_server(&conn); + + rv = ngtcp2_conn_handle_expiry( + conn, conn->local.settings.initial_ts + + conn->local.settings.handshake_timeout - 1); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); + + /* handshake timeout should be ignored after handshake has + completed. */ + setup_default_server(&conn); + + rv = ngtcp2_conn_handle_expiry(conn, + conn->local.settings.initial_ts + + conn->local.settings.handshake_timeout); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_get_connection_close_error(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_frame frs[2]; + size_t pktlen; + uint8_t reason[2048]; + ngtcp2_tstamp t = 0; + int64_t pkt_num = 0; + int rv; + ngtcp2_connection_close_error ccerr; + + memset(reason, 'a', sizeof(reason)); + + setup_default_server(&conn); + + /* Record the last error. */ + frs[0].type = NGTCP2_FRAME_CONNECTION_CLOSE_APP; + frs[0].connection_close.error_code = 1; + frs[0].connection_close.frame_type = 99; + frs[0].connection_close.reasonlen = 10; + frs[0].connection_close.reason = reason; + + frs[1].type = NGTCP2_FRAME_CONNECTION_CLOSE; + frs[1].connection_close.error_code = NGTCP2_PROTOCOL_VIOLATION; + frs[1].connection_close.frame_type = 1000000007; + frs[1].connection_close.reasonlen = + NGTCP2_CONNECTION_CLOSE_ERROR_MAX_REASONLEN + 1; + frs[1].connection_close.reason = reason; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, ++pkt_num, frs, + ngtcp2_arraylen(frs), conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(NGTCP2_ERR_DRAINING == rv); + + ngtcp2_conn_get_connection_close_error(conn, &ccerr); + + CU_ASSERT(NGTCP2_PROTOCOL_VIOLATION == ccerr.error_code); + CU_ASSERT(NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT == ccerr.type); + CU_ASSERT(1000000007 == ccerr.frame_type); + CU_ASSERT(0 == memcmp(reason, ccerr.reason, ccerr.reasonlen)); + CU_ASSERT(NGTCP2_CONNECTION_CLOSE_ERROR_MAX_REASONLEN == ccerr.reasonlen); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_version_negotiation(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_frame fr; + ngtcp2_tstamp t = 0; + ngtcp2_ssize spktlen; + int64_t pkt_num = 0; + size_t pktlen; + int rv; + ngtcp2_transport_params remote_params; + uint8_t other_versions[sizeof(uint32_t) * 2]; + uint32_t version; + + ngtcp2_put_uint32be(&other_versions[0], NGTCP2_PROTO_VER_V1); + ngtcp2_put_uint32be(&other_versions[4], NGTCP2_PROTO_VER_V2_DRAFT); + + /* Client sees the change version in Initial packet which contains + CRYPTO frame. It generates new Initial keys and sets negotiated + version. */ + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 133; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &conn->oscid, ngtcp2_conn_get_dcid(conn), pkt_num++, + NGTCP2_PROTO_VER_V2_DRAFT, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_PROTO_VER_V2_DRAFT == conn->negotiated_version); + CU_ASSERT(NGTCP2_PROTO_VER_V2_DRAFT == conn->vneg.version); + CU_ASSERT(conn->vneg.rx.ckm != NULL); + CU_ASSERT(conn->vneg.tx.ckm != NULL); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + ngtcp2_get_uint32(&version, &buf[1]); + + CU_ASSERT(NGTCP2_PROTO_VER_V2_DRAFT == version); + + ngtcp2_conn_del(conn); + + /* Client receives Initial packet which does not change version and + does not contain CRYPTO frame. It leaves negotiated version + unchanged. */ + setup_handshake_client(&conn); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_PADDING; + fr.padding.len = 1; + + pktlen = write_initial_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), pkt_num++, + NGTCP2_PROTO_VER_V1, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == conn->negotiated_version); + CU_ASSERT(0 == conn->vneg.version); + CU_ASSERT(conn->vneg.rx.ckm == NULL); + CU_ASSERT(conn->vneg.tx.ckm == NULL); + + ngtcp2_conn_del(conn); + + /* Server sees client supports QUIC v2. It chooses QUIC v2 as the + negotiated version, and generates new Initial keys. */ + setup_handshake_server(&conn); + + conn->callbacks.recv_client_initial = + recv_client_initial_no_remote_transport_params; + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1233; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt(buf, sizeof(buf), &conn->oscid, + ngtcp2_conn_get_dcid(conn), pkt_num++, + NGTCP2_PROTO_VER_V1, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + ngtcp2_transport_params_default(&remote_params); + ngtcp2_cid_init(&remote_params.initial_scid, conn->dcid.current.cid.data, + conn->dcid.current.cid.datalen); + remote_params.version_info_present = 1; + remote_params.version_info.chosen_version = NGTCP2_PROTO_VER_V1; + remote_params.version_info.other_versions = other_versions; + remote_params.version_info.other_versionslen = sizeof(other_versions); + + rv = ngtcp2_conn_set_remote_transport_params(conn, &remote_params); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_PROTO_VER_V2_DRAFT == conn->negotiated_version); + CU_ASSERT(NGTCP2_PROTO_VER_V2_DRAFT == conn->vneg.version); + CU_ASSERT(conn->vneg.rx.ckm != NULL); + CU_ASSERT(conn->vneg.tx.ckm != NULL); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + ngtcp2_get_uint32(&version, &buf[1]); + + CU_ASSERT(NGTCP2_PROTO_VER_V2_DRAFT == version); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_server_negotiate_version(void) { + ngtcp2_conn *conn; + ngtcp2_version_info version_info = {0}; + uint8_t client_other_versions[sizeof(uint32_t) * 2]; + + setup_handshake_server(&conn); + + version_info.chosen_version = conn->client_chosen_version; + + /* Empty version_info.other_versions */ + version_info.other_versions = NULL; + version_info.other_versionslen = 0; + + CU_ASSERT(conn->client_chosen_version == + ngtcp2_conn_server_negotiate_version(conn, &version_info)); + + /* version_info.other_versions and preferred_versions do not share + any version. */ + ngtcp2_put_uint32be(&client_other_versions[0], 0xff000001); + ngtcp2_put_uint32be(&client_other_versions[4], 0xff000002); + + version_info.other_versions = client_other_versions; + version_info.other_versionslen = sizeof(uint32_t) * 2; + + CU_ASSERT(conn->client_chosen_version == + ngtcp2_conn_server_negotiate_version(conn, &version_info)); + + /* version_info.other_versions and preferred_versions share the + version. */ + ngtcp2_put_uint32be(&client_other_versions[0], 0xff000001); + ngtcp2_put_uint32be(&client_other_versions[4], NGTCP2_PROTO_VER_V2_DRAFT); + + version_info.other_versions = client_other_versions; + version_info.other_versionslen = sizeof(uint32_t) * 2; + + CU_ASSERT(NGTCP2_PROTO_VER_V2_DRAFT == + ngtcp2_conn_server_negotiate_version(conn, &version_info)); + + ngtcp2_conn_del(conn); + + /* Without preferred_versions */ + setup_handshake_server(&conn); + + ngtcp2_mem_free(conn->mem, conn->vneg.preferred_versions); + conn->vneg.preferred_versions = NULL; + conn->vneg.preferred_versionslen = 0; + + ngtcp2_put_uint32be(&client_other_versions[0], 0xff000001); + ngtcp2_put_uint32be(&client_other_versions[4], NGTCP2_PROTO_VER_V2_DRAFT); + + version_info.other_versions = client_other_versions; + version_info.other_versionslen = sizeof(uint32_t) * 2; + + CU_ASSERT(conn->client_chosen_version == + ngtcp2_conn_server_negotiate_version(conn, &version_info)); + + ngtcp2_conn_del(conn); + + /* original version is the most preferred version */ + setup_handshake_server(&conn); + + conn->vneg.preferred_versions[0] = NGTCP2_PROTO_VER_V1; + conn->vneg.preferred_versions[1] = NGTCP2_PROTO_VER_V2_DRAFT; + + ngtcp2_put_uint32be(&client_other_versions[0], NGTCP2_PROTO_VER_V2_DRAFT); + ngtcp2_put_uint32be(&client_other_versions[4], NGTCP2_PROTO_VER_V1); + + version_info.other_versions = client_other_versions; + version_info.other_versionslen = sizeof(uint32_t) * 2; + + CU_ASSERT(conn->client_chosen_version == + ngtcp2_conn_server_negotiate_version(conn, &version_info)); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_pmtud_loss(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_ssize spktlen; + uint64_t t = 0; + ngtcp2_frame fr; + int64_t pkt_num = 0; + size_t pktlen; + int rv; + + setup_default_client(&conn); + + ngtcp2_conn_start_pmtud(conn); + + /* This sends PMTUD packet. */ + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(1406 == spktlen); + + t += NGTCP2_SECONDS; + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + fr.type = NGTCP2_FRAME_ACK; + fr.ack.largest_ack = conn->pktns.tx.last_pkt_num; + fr.ack.ack_delay = 0; + fr.ack.first_ack_range = 0; + fr.ack.rangecnt = 0; + + pktlen = write_pkt(buf, sizeof(buf), &conn->oscid, pkt_num++, &fr, 1, + conn->pktns.crypto.rx.ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == conn->pktns.rtb.num_lost_pkts); + CU_ASSERT(1 == conn->pktns.rtb.num_lost_pmtud_pkts); + CU_ASSERT(0 == conn->pktns.rtb.cc_bytes_in_flight); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_conn_amplification(void) { + ngtcp2_conn *conn; + ngtcp2_frame fr; + size_t pktlen; + uint8_t buf[2048]; + ngtcp2_cid rcid; + int64_t pkt_num = 0; + ngtcp2_tstamp t = 0; + ngtcp2_ssize spktlen; + int rv; + + rcid_init(&rcid); + + /* ACK only frame should not be sent due to amplification limit. */ + setup_early_server(&conn); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1200; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt( + buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), ++pkt_num, + conn->client_chosen_version, NULL, 0, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.stream_id = 4; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 111; + fr.stream.data[0].base = null_data; + + pktlen = + write_0rtt_pkt(buf, sizeof(buf), &rcid, ngtcp2_conn_get_dcid(conn), + ++pkt_num, conn->client_chosen_version, &fr, 1, &null_ckm); + + rv = ngtcp2_conn_read_pkt(conn, &null_path.path, &null_pi, buf, pktlen, ++t); + + CU_ASSERT(0 == rv); + + /* Adjust condition so that the execution path goes into sending ACK + only frame. */ + conn->dcid.current.bytes_sent = conn->dcid.current.bytes_recv * 3 - 1; + conn->cstat.bytes_in_flight = conn->cstat.cwnd; + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(0 == spktlen); + + ngtcp2_conn_del(conn); +} + +typedef struct failmalloc { + size_t nmalloc; + size_t fail_start; +} failmalloc; + +static void *failmalloc_malloc(size_t size, void *user_data) { + failmalloc *mc = user_data; + + if (mc->fail_start <= ++mc->nmalloc) { + return NULL; + } + + return malloc(size); +} + +static void failmalloc_free(void *ptr, void *user_data) { + (void)user_data; + + free(ptr); +} + +static void *failmalloc_calloc(size_t nmemb, size_t size, void *user_data) { + failmalloc *mc = user_data; + + if (mc->fail_start <= ++mc->nmalloc) { + return NULL; + } + + return calloc(nmemb, size); +} + +static void *failmalloc_realloc(void *ptr, size_t size, void *user_data) { + failmalloc *mc = user_data; + + if (mc->fail_start <= ++mc->nmalloc) { + return NULL; + } + + return realloc(ptr, size); +} + +void test_ngtcp2_conn_new_failmalloc(void) { + ngtcp2_conn *conn; + ngtcp2_callbacks cb; + ngtcp2_settings settings; + ngtcp2_transport_params params; + failmalloc mc; + ngtcp2_mem mem = { + &mc, + failmalloc_malloc, + failmalloc_free, + failmalloc_calloc, + failmalloc_realloc, + }; + ngtcp2_vec token = { + (uint8_t *)"token", + sizeof("token") - 1, + }; + uint32_t preferred_versions[] = { + NGTCP2_PROTO_VER_V1, + NGTCP2_PROTO_VER_V2_DRAFT, + }; + uint32_t other_versions[] = { + NGTCP2_PROTO_VER_V2_DRAFT, + NGTCP2_PROTO_VER_V1, + 0x5a9aeaca, + }; + ngtcp2_cid dcid, scid; + int rv; + size_t i; + size_t nmalloc; + + dcid_init(&dcid); + scid_init(&scid); + + ngtcp2_settings_default(&settings); + ngtcp2_transport_params_default(¶ms); + + settings.qlog.write = qlog_write; + settings.token = token; + settings.preferred_versions = preferred_versions; + settings.preferred_versionslen = ngtcp2_arraylen(preferred_versions); + settings.other_versions = other_versions; + settings.other_versionslen = ngtcp2_arraylen(other_versions); + + /* server */ + server_default_callbacks(&cb); + + mc.nmalloc = 0; + mc.fail_start = SIZE_MAX; + + rv = ngtcp2_conn_server_new(&conn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + &mem, NULL); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); + + nmalloc = mc.nmalloc; + + for (i = 0; i <= nmalloc; ++i) { + mc.nmalloc = 0; + mc.fail_start = i; + + rv = ngtcp2_conn_server_new(&conn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + &mem, NULL); + + CU_ASSERT(NGTCP2_ERR_NOMEM == rv); + } + + mc.nmalloc = 0; + mc.fail_start = nmalloc + 1; + + rv = ngtcp2_conn_server_new(&conn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + &mem, NULL); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); + + /* client */ + client_default_callbacks(&cb); + + mc.nmalloc = 0; + mc.fail_start = SIZE_MAX; + + rv = ngtcp2_conn_client_new(&conn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + &mem, NULL); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); + + nmalloc = mc.nmalloc; + + for (i = 0; i <= nmalloc; ++i) { + mc.nmalloc = 0; + mc.fail_start = i; + + rv = ngtcp2_conn_client_new(&conn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + &mem, NULL); + + CU_ASSERT(NGTCP2_ERR_NOMEM == rv); + } + + mc.nmalloc = 0; + mc.fail_start = nmalloc + 1; + + rv = ngtcp2_conn_client_new(&conn, &dcid, &scid, &null_path.path, + NGTCP2_PROTO_VER_V1, &cb, &settings, ¶ms, + &mem, NULL); + + CU_ASSERT(0 == rv); + + ngtcp2_conn_del(conn); +} + +void test_ngtcp2_accept(void) { + size_t pktlen; + uint8_t buf[2048]; + ngtcp2_cid dcid, scid; + ngtcp2_frame fr; + int rv; + ngtcp2_pkt_hd hd; + + dcid_init(&dcid); + scid_init(&scid); + + /* Initial packet */ + memset(&hd, 0, sizeof(hd)); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1200; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt(buf, sizeof(buf), &dcid, &scid, 0, + NGTCP2_PROTO_VER_V1, NULL, 0, &fr, 1, &null_ckm); + + CU_ASSERT(pktlen >= 1200); + + rv = ngtcp2_accept(&hd, buf, pktlen); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_cid_eq(&dcid, &hd.dcid)); + CU_ASSERT(ngtcp2_cid_eq(&scid, &hd.scid)); + CU_ASSERT(0 == hd.token.len); + CU_ASSERT(hd.len > 0); + CU_ASSERT(NGTCP2_PROTO_VER_V1 == hd.version); + CU_ASSERT(NGTCP2_PKT_INITIAL == hd.type); + CU_ASSERT(hd.flags & NGTCP2_PKT_FLAG_LONG_FORM); + + /* 0RTT packet */ + memset(&hd, 0, sizeof(hd)); + + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.flags = 0; + fr.stream.stream_id = 0; + fr.stream.fin = 0; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = 1200; + fr.stream.data[0].base = null_data; + + pktlen = write_0rtt_pkt(buf, sizeof(buf), &dcid, &scid, 1, + NGTCP2_PROTO_VER_V1, &fr, 1, &null_ckm); + + CU_ASSERT(pktlen >= 1200); + + rv = ngtcp2_accept(&hd, buf, pktlen); + + CU_ASSERT(NGTCP2_ERR_RETRY == rv); + CU_ASSERT(ngtcp2_cid_eq(&dcid, &hd.dcid)); + CU_ASSERT(ngtcp2_cid_eq(&scid, &hd.scid)); + CU_ASSERT(0 == hd.token.len); + CU_ASSERT(hd.len > 0); + CU_ASSERT(NGTCP2_PROTO_VER_V1 == hd.version); + CU_ASSERT(NGTCP2_PKT_0RTT == hd.type); + CU_ASSERT(hd.flags & NGTCP2_PKT_FLAG_LONG_FORM); + + /* Unknown version */ + memset(&hd, 0, sizeof(hd)); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1200; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt(buf, sizeof(buf), &dcid, &scid, 0, 0x2, NULL, 0, + &fr, 1, &null_ckm); + + CU_ASSERT(pktlen >= 1200); + + rv = ngtcp2_accept(&hd, buf, pktlen); + + /* Unknown version should be filtered out by earlier call of + ngtcp2_pkt_decode_version_cid, that is, only supported versioned + packet should be passed to ngtcp2_accept. */ + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv); + + /* Unknown version and the UDP payload size is less than + NGTCP2_MAX_UDP_PAYLOAD_SIZE. */ + memset(&hd, 0, sizeof(hd)); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1127; + fr.crypto.data[0].base = null_data; + + pktlen = write_initial_pkt(buf, sizeof(buf), &dcid, &scid, 0, 0x2, NULL, 0, + &fr, 1, &null_ckm); + + CU_ASSERT(1199 == pktlen); + + rv = ngtcp2_accept(&hd, buf, pktlen); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv); + + /* Short packet */ + memset(&hd, 0, sizeof(hd)); + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = 1200; + fr.crypto.data[0].base = null_data; + + pktlen = write_pkt(buf, sizeof(buf), &dcid, 0, &fr, 1, &null_ckm); + + CU_ASSERT(pktlen >= 1200); + + rv = ngtcp2_accept(&hd, buf, pktlen); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv); + + /* Unable to decode packet header */ + memset(&hd, 0, sizeof(hd)); + + memset(buf, 0, 4); + buf[0] = NGTCP2_HEADER_FORM_BIT; + + rv = ngtcp2_accept(&hd, buf, 4); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv); +} + +void test_ngtcp2_select_version(void) { + CU_ASSERT(0 == ngtcp2_select_version(NULL, 0, NULL, 0)); + + { + uint32_t preferred_versions[] = {NGTCP2_PROTO_VER_V1, + NGTCP2_PROTO_VER_V2_DRAFT}; + uint32_t offered_versions[] = {0x00000004, 0x00000003, + NGTCP2_PROTO_VER_V2_DRAFT}; + + CU_ASSERT(NGTCP2_PROTO_VER_V2_DRAFT == + ngtcp2_select_version( + preferred_versions, ngtcp2_arraylen(preferred_versions), + offered_versions, ngtcp2_arraylen(offered_versions))); + } + + { + uint32_t preferred_versions[] = {NGTCP2_PROTO_VER_V1, + NGTCP2_PROTO_VER_V2_DRAFT}; + uint32_t offered_versions[] = {0x00000004, 0x00000003}; + + CU_ASSERT(0 == ngtcp2_select_version( + preferred_versions, ngtcp2_arraylen(preferred_versions), + offered_versions, ngtcp2_arraylen(offered_versions))); + } +} + +void test_ngtcp2_pkt_write_connection_close(void) { + ngtcp2_ssize spktlen; + uint8_t buf[1200]; + ngtcp2_cid dcid, scid; + ngtcp2_crypto_aead aead = {0, NGTCP2_INITIAL_AEAD_OVERHEAD}; + ngtcp2_crypto_cipher hp_mask = {0}; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + ngtcp2_crypto_cipher_ctx hp_ctx = {0}; + + dcid_init(&dcid); + scid_init(&scid); + + spktlen = ngtcp2_pkt_write_connection_close( + buf, sizeof(buf), NGTCP2_PROTO_VER_V1, &dcid, &scid, NGTCP2_INVALID_TOKEN, + (const uint8_t *)"foo", 3, null_encrypt, &aead, &aead_ctx, null_iv, + null_hp_mask, &hp_mask, &hp_ctx); + + CU_ASSERT(spktlen > 0); + + spktlen = ngtcp2_pkt_write_connection_close( + buf, 16, NGTCP2_PROTO_VER_V1, &dcid, &scid, NGTCP2_INVALID_TOKEN, NULL, 0, + null_encrypt, &aead, &aead_ctx, null_iv, null_hp_mask, &hp_mask, &hp_ctx); + + CU_ASSERT(NGTCP2_ERR_NOBUF == spktlen); +} diff --git a/tests/ngtcp2_conn_test.h b/tests/ngtcp2_conn_test.h new file mode 100644 index 0000000..b3c2b3a --- /dev/null +++ b/tests/ngtcp2_conn_test.h @@ -0,0 +1,103 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CONN_TEST_H +#define NGTCP2_CONN_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void init_static_path(void); + +void test_ngtcp2_conn_stream_open_close(void); +void test_ngtcp2_conn_stream_rx_flow_control(void); +void test_ngtcp2_conn_stream_rx_flow_control_error(void); +void test_ngtcp2_conn_stream_tx_flow_control(void); +void test_ngtcp2_conn_rx_flow_control(void); +void test_ngtcp2_conn_rx_flow_control_error(void); +void test_ngtcp2_conn_tx_flow_control(void); +void test_ngtcp2_conn_shutdown_stream_write(void); +void test_ngtcp2_conn_recv_reset_stream(void); +void test_ngtcp2_conn_recv_stop_sending(void); +void test_ngtcp2_conn_recv_conn_id_omitted(void); +void test_ngtcp2_conn_short_pkt_type(void); +void test_ngtcp2_conn_recv_stateless_reset(void); +void test_ngtcp2_conn_recv_retry(void); +void test_ngtcp2_conn_recv_delayed_handshake_pkt(void); +void test_ngtcp2_conn_recv_max_streams(void); +void test_ngtcp2_conn_handshake(void); +void test_ngtcp2_conn_handshake_error(void); +void test_ngtcp2_conn_retransmit_protected(void); +void test_ngtcp2_conn_send_max_stream_data(void); +void test_ngtcp2_conn_recv_stream_data(void); +void test_ngtcp2_conn_recv_ping(void); +void test_ngtcp2_conn_recv_max_stream_data(void); +void test_ngtcp2_conn_send_early_data(void); +void test_ngtcp2_conn_recv_early_data(void); +void test_ngtcp2_conn_recv_compound_pkt(void); +void test_ngtcp2_conn_pkt_payloadlen(void); +void test_ngtcp2_conn_writev_stream(void); +void test_ngtcp2_conn_writev_datagram(void); +void test_ngtcp2_conn_recv_datagram(void); +void test_ngtcp2_conn_recv_new_connection_id(void); +void test_ngtcp2_conn_recv_retire_connection_id(void); +void test_ngtcp2_conn_server_path_validation(void); +void test_ngtcp2_conn_client_connection_migration(void); +void test_ngtcp2_conn_recv_path_challenge(void); +void test_ngtcp2_conn_key_update(void); +void test_ngtcp2_conn_crypto_buffer_exceeded(void); +void test_ngtcp2_conn_handshake_probe(void); +void test_ngtcp2_conn_handshake_loss(void); +void test_ngtcp2_conn_recv_client_initial_retry(void); +void test_ngtcp2_conn_recv_client_initial_token(void); +void test_ngtcp2_conn_get_active_dcid(void); +void test_ngtcp2_conn_recv_version_negotiation(void); +void test_ngtcp2_conn_send_initial_token(void); +void test_ngtcp2_conn_set_remote_transport_params(void); +void test_ngtcp2_conn_write_connection_close(void); +void test_ngtcp2_conn_write_application_close(void); +void test_ngtcp2_conn_rtb_reclaim_on_pto(void); +void test_ngtcp2_conn_rtb_reclaim_on_pto_datagram(void); +void test_ngtcp2_conn_validate_ecn(void); +void test_ngtcp2_conn_path_validation(void); +void test_ngtcp2_conn_early_data_sync_stream_data_limit(void); +void test_ngtcp2_conn_early_data_rejected(void); +void test_ngtcp2_conn_keep_alive(void); +void test_ngtcp2_conn_retire_stale_bound_dcid(void); +void test_ngtcp2_conn_get_scid(void); +void test_ngtcp2_conn_stream_close(void); +void test_ngtcp2_conn_buffer_pkt(void); +void test_ngtcp2_conn_handshake_timeout(void); +void test_ngtcp2_conn_get_connection_close_error(void); +void test_ngtcp2_conn_version_negotiation(void); +void test_ngtcp2_conn_server_negotiate_version(void); +void test_ngtcp2_conn_pmtud_loss(void); +void test_ngtcp2_conn_amplification(void); +void test_ngtcp2_conn_new_failmalloc(void); +void test_ngtcp2_accept(void); +void test_ngtcp2_select_version(void); +void test_ngtcp2_pkt_write_connection_close(void); + +#endif /* NGTCP2_CONN_TEST_H */ diff --git a/tests/ngtcp2_conv_test.c b/tests/ngtcp2_conv_test.c new file mode 100644 index 0000000..7b92ec7 --- /dev/null +++ b/tests/ngtcp2_conv_test.c @@ -0,0 +1,430 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_conv_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_conv.h" +#include "ngtcp2_net.h" +#include "ngtcp2_test_helper.h" + +void test_ngtcp2_get_varint(void) { + uint8_t buf[256]; + const uint8_t *p; + uint64_t n; + int64_t s; + + /* 0 */ + n = 1; + p = ngtcp2_put_uvarint(buf, 0); + + CU_ASSERT(1 == p - buf); + + p = ngtcp2_get_uvarint(&n, buf); + + CU_ASSERT(1 == p - buf); + CU_ASSERT(0 == n); + + /* 63 */ + n = 0; + p = ngtcp2_put_uvarint(buf, 63); + + CU_ASSERT(1 == p - buf); + + p = ngtcp2_get_uvarint(&n, buf); + + CU_ASSERT(1 == p - buf); + CU_ASSERT(63 == n); + + /* 64 */ + n = 0; + p = ngtcp2_put_uvarint(buf, 64); + + CU_ASSERT(2 == p - buf); + + p = ngtcp2_get_uvarint(&n, buf); + + CU_ASSERT(2 == p - buf); + CU_ASSERT(64 == n); + + /* 16383 */ + n = 0; + p = ngtcp2_put_uvarint(buf, 16383); + + CU_ASSERT(2 == p - buf); + + p = ngtcp2_get_uvarint(&n, buf); + + CU_ASSERT(2 == p - buf); + CU_ASSERT(16383 == n); + + /* 16384 */ + n = 0; + p = ngtcp2_put_uvarint(buf, 16384); + + CU_ASSERT(4 == p - buf); + + p = ngtcp2_get_uvarint(&n, buf); + + CU_ASSERT(4 == p - buf); + CU_ASSERT(16384 == n); + + /* 1073741823 */ + n = 0; + p = ngtcp2_put_uvarint(buf, 1073741823); + + CU_ASSERT(4 == p - buf); + + p = ngtcp2_get_uvarint(&n, buf); + + CU_ASSERT(4 == p - buf); + CU_ASSERT(1073741823 == n); + + /* 1073741824 */ + n = 0; + p = ngtcp2_put_uvarint(buf, 1073741824); + + CU_ASSERT(8 == p - buf); + + p = ngtcp2_get_uvarint(&n, buf); + + CU_ASSERT(8 == p - buf); + CU_ASSERT(1073741824 == n); + + /* 4611686018427387903 */ + n = 0; + p = ngtcp2_put_uvarint(buf, 4611686018427387903ULL); + + CU_ASSERT(8 == p - buf); + + p = ngtcp2_get_uvarint(&n, buf); + + CU_ASSERT(8 == p - buf); + CU_ASSERT(4611686018427387903ULL == n); + + /* Check signed version */ + s = 0; + p = ngtcp2_put_uvarint(buf, 4611686018427387903ULL); + + CU_ASSERT(8 == p - buf); + + p = ngtcp2_get_varint(&s, buf); + + CU_ASSERT(8 == p - buf); + CU_ASSERT(4611686018427387903LL == s); +} + +void test_ngtcp2_get_uvarintlen(void) { + uint8_t c; + + c = 0x00; + + CU_ASSERT(1 == ngtcp2_get_uvarintlen(&c)); + + c = 0x40; + + CU_ASSERT(2 == ngtcp2_get_uvarintlen(&c)); + + c = 0x80; + + CU_ASSERT(4 == ngtcp2_get_uvarintlen(&c)); + + c = 0xc0; + + CU_ASSERT(8 == ngtcp2_get_uvarintlen(&c)); +} + +void test_ngtcp2_get_uint64(void) { + uint8_t buf[256]; + const uint8_t *p; + uint64_t n; + + /* 0 */ + n = 1; + p = ngtcp2_put_uint64be(buf, 0); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint64(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(0 == n); + + /* 12345678900 */ + n = 0; + p = ngtcp2_put_uint64be(buf, 12345678900ULL); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint64(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(12345678900ULL == n); + + /* 18446744073709551615 */ + n = 0; + p = ngtcp2_put_uint64be(buf, 18446744073709551615ULL); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint64(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(18446744073709551615ULL == n); +} + +void test_ngtcp2_get_uint48(void) { + uint8_t buf[256]; + const uint8_t *p; + uint64_t n; + + /* 0 */ + n = 1; + p = ngtcp2_put_uint48be(buf, 0); + + CU_ASSERT(6 == p - buf); + + p = ngtcp2_get_uint48(&n, buf); + + CU_ASSERT(6 == p - buf); + CU_ASSERT(0 == n); + + /* 123456789 */ + n = 0; + p = ngtcp2_put_uint48be(buf, 123456789); + + CU_ASSERT(6 == p - buf); + + p = ngtcp2_get_uint48(&n, buf); + + CU_ASSERT(6 == p - buf); + CU_ASSERT(123456789 == n); + + /* 281474976710655 */ + n = 0; + p = ngtcp2_put_uint48be(buf, 281474976710655ULL); + + CU_ASSERT(6 == p - buf); + + p = ngtcp2_get_uint48(&n, buf); + + CU_ASSERT(6 == p - buf); + CU_ASSERT(281474976710655ULL == n); +} + +void test_ngtcp2_get_uint32(void) { + uint8_t buf[256]; + const uint8_t *p; + uint32_t n; + + /* 0 */ + n = 1; + p = ngtcp2_put_uint32be(buf, 0); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint32(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(0 == n); + + /* 123456 */ + n = 0; + p = ngtcp2_put_uint32be(buf, 123456); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint32(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(123456 == n); + + /* 4294967295 */ + n = 0; + p = ngtcp2_put_uint32be(buf, 4294967295UL); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint32(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(4294967295UL == n); +} + +void test_ngtcp2_get_uint24(void) { + uint8_t buf[256]; + const uint8_t *p; + uint32_t n; + + /* 0 */ + n = 1; + p = ngtcp2_put_uint24be(buf, 0); + + CU_ASSERT(3 == p - buf); + + p = ngtcp2_get_uint24(&n, buf); + + CU_ASSERT(3 == p - buf); + CU_ASSERT(0 == n); + + /* 12345 */ + n = 0; + p = ngtcp2_put_uint24be(buf, 12345); + + CU_ASSERT(3 == p - buf); + + p = ngtcp2_get_uint24(&n, buf); + + CU_ASSERT(3 == p - buf); + CU_ASSERT(12345 == n); + + /* 16777215 */ + n = 0; + p = ngtcp2_put_uint24be(buf, 16777215); + + CU_ASSERT(3 == p - buf); + + p = ngtcp2_get_uint24(&n, buf); + + CU_ASSERT(3 == p - buf); + CU_ASSERT(16777215 == n); +} + +void test_ngtcp2_get_uint16(void) { + uint8_t buf[256]; + const uint8_t *p; + uint16_t n; + + /* 0 */ + n = 1; + p = ngtcp2_put_uint16be(buf, 0); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint16(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(0 == n); + + /* 1234 */ + n = 0; + p = ngtcp2_put_uint16be(buf, 1234); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint16(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(1234 == n); + + /* 65535 */ + n = 0; + p = ngtcp2_put_uint16be(buf, 65535); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint16(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(65535 == n); +} + +void test_ngtcp2_get_uint16be(void) { + uint8_t buf[256]; + const uint8_t *p; + uint16_t n; + + /* 0 */ + n = 1; + p = ngtcp2_put_uint16(buf, 0); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint16be(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(0 == n); + + /* 1234 */ + n = 0; + p = ngtcp2_put_uint16(buf, ngtcp2_htons(1234)); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint16be(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(1234 == ngtcp2_ntohs(n)); + + /* 65535 */ + n = 0; + p = ngtcp2_put_uint16(buf, 65535); + + CU_ASSERT(sizeof(n) == p - buf); + + p = ngtcp2_get_uint16be(&n, buf); + + CU_ASSERT(sizeof(n) == p - buf); + CU_ASSERT(65535 == n); +} + +void test_ngtcp2_put_uvarintlen(void) { + CU_ASSERT(1 == ngtcp2_put_uvarintlen(0)); + CU_ASSERT(1 == ngtcp2_put_uvarintlen(63)); + CU_ASSERT(2 == ngtcp2_put_uvarintlen(64)); + CU_ASSERT(2 == ngtcp2_put_uvarintlen(16383)); + CU_ASSERT(4 == ngtcp2_put_uvarintlen(16384)); + CU_ASSERT(4 == ngtcp2_put_uvarintlen(1073741823)); + CU_ASSERT(8 == ngtcp2_put_uvarintlen(1073741824)); + CU_ASSERT(8 == ngtcp2_put_uvarintlen(4611686018427387903ULL)); +} + +void test_ngtcp2_nth_server_bidi_id(void) { + CU_ASSERT(0 == ngtcp2_nth_server_bidi_id(0)); + CU_ASSERT(1 == ngtcp2_nth_server_bidi_id(1)); + CU_ASSERT(5 == ngtcp2_nth_server_bidi_id(2)); + CU_ASSERT(9 == ngtcp2_nth_server_bidi_id(3)); +} + +void test_ngtcp2_nth_server_uni_id(void) { + CU_ASSERT(0 == ngtcp2_nth_server_uni_id(0)); + CU_ASSERT(3 == ngtcp2_nth_server_uni_id(1)); + CU_ASSERT(7 == ngtcp2_nth_server_uni_id(2)); + CU_ASSERT(11 == ngtcp2_nth_server_uni_id(3)); +} + +void test_ngtcp2_nth_client_bidi_id(void) { + CU_ASSERT(0 == ngtcp2_nth_client_bidi_id(0)); + CU_ASSERT(0 == ngtcp2_nth_client_bidi_id(1)); + CU_ASSERT(4 == ngtcp2_nth_client_bidi_id(2)); + CU_ASSERT(8 == ngtcp2_nth_client_bidi_id(3)); +} + +void test_ngtcp2_nth_client_uni_id(void) { + CU_ASSERT(0 == ngtcp2_nth_client_uni_id(0)); + CU_ASSERT(2 == ngtcp2_nth_client_uni_id(1)); + CU_ASSERT(6 == ngtcp2_nth_client_uni_id(2)); + CU_ASSERT(10 == ngtcp2_nth_client_uni_id(3)); +} diff --git a/tests/ngtcp2_conv_test.h b/tests/ngtcp2_conv_test.h new file mode 100644 index 0000000..1cd4324 --- /dev/null +++ b/tests/ngtcp2_conv_test.h @@ -0,0 +1,46 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CONV_TEST_H +#define NGTCP2_CONV_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_get_varint(void); +void test_ngtcp2_get_uvarintlen(void); +void test_ngtcp2_put_uvarintlen(void); +void test_ngtcp2_get_uint64(void); +void test_ngtcp2_get_uint48(void); +void test_ngtcp2_get_uint32(void); +void test_ngtcp2_get_uint24(void); +void test_ngtcp2_get_uint16(void); +void test_ngtcp2_get_uint16be(void); +void test_ngtcp2_nth_server_bidi_id(void); +void test_ngtcp2_nth_server_uni_id(void); +void test_ngtcp2_nth_client_bidi_id(void); +void test_ngtcp2_nth_client_uni_id(void); + +#endif /* NGTCP2_CONV_TEST_H */ diff --git a/tests/ngtcp2_crypto_test.c b/tests/ngtcp2_crypto_test.c new file mode 100644 index 0000000..7469968 --- /dev/null +++ b/tests/ngtcp2_crypto_test.c @@ -0,0 +1,665 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_crypto_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_crypto.h" +#include "ngtcp2_cid.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_net.h" +#include "ngtcp2_test_helper.h" + +static size_t varint_paramlen(ngtcp2_transport_param_id id, uint64_t value) { + size_t valuelen = ngtcp2_put_uvarintlen(value); + return ngtcp2_put_uvarintlen(id) + ngtcp2_put_uvarintlen(valuelen) + valuelen; +} + +void test_ngtcp2_encode_transport_params(void) { + ngtcp2_transport_params params, nparams; + uint8_t buf[512]; + ngtcp2_ssize nwrite; + int rv; + size_t i, len; + ngtcp2_cid rcid, scid, dcid; + uint8_t other_versions[sizeof(uint32_t) * 3]; + ngtcp2_sockaddr_in6 *sa_in6; + + rcid_init(&rcid); + scid_init(&scid); + dcid_init(&dcid); + + memset(¶ms, 0, sizeof(params)); + memset(&nparams, 0, sizeof(nparams)); + + for (i = 0; i < sizeof(other_versions); i += sizeof(uint32_t)) { + ngtcp2_put_uint32be(&other_versions[i], (uint32_t)(0xff000000u + i)); + } + + /* CH, required parameters only */ + params.max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + params.ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + params.max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; + params.initial_scid = scid; + + len = (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.initial_scid.datalen) + + params.initial_scid.datalen); + + nwrite = ngtcp2_encode_transport_params( + buf, sizeof(buf), NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms); + + CU_ASSERT((ngtcp2_ssize)len == nwrite); + + rv = ngtcp2_decode_transport_params( + &nparams, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, buf, (size_t)nwrite); + + CU_ASSERT(0 == rv); + CU_ASSERT(params.initial_max_stream_data_bidi_local == + nparams.initial_max_stream_data_bidi_local); + CU_ASSERT(params.initial_max_stream_data_bidi_remote == + nparams.initial_max_stream_data_bidi_remote); + CU_ASSERT(params.initial_max_stream_data_uni == + nparams.initial_max_stream_data_uni); + CU_ASSERT(params.initial_max_data == nparams.initial_max_data); + CU_ASSERT(params.initial_max_streams_bidi == + nparams.initial_max_streams_bidi); + CU_ASSERT(params.initial_max_streams_uni == nparams.initial_max_streams_uni); + CU_ASSERT(params.max_idle_timeout == nparams.max_idle_timeout); + CU_ASSERT(params.max_udp_payload_size == nparams.max_udp_payload_size); + CU_ASSERT(params.ack_delay_exponent == nparams.ack_delay_exponent); + CU_ASSERT(params.stateless_reset_token_present == + nparams.stateless_reset_token_present); + CU_ASSERT(params.disable_active_migration == + nparams.disable_active_migration); + CU_ASSERT(params.max_ack_delay == nparams.max_ack_delay); + CU_ASSERT(ngtcp2_cid_eq(¶ms.initial_scid, &nparams.initial_scid)); + + memset(¶ms, 0, sizeof(params)); + memset(&nparams, 0, sizeof(nparams)); + + /* EE, required parameters only */ + params.max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + params.ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + params.max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; + params.original_dcid = dcid; + params.initial_scid = scid; + + len = (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.original_dcid.datalen) + + params.original_dcid.datalen) + + (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.initial_scid.datalen) + + params.initial_scid.datalen); + + nwrite = ngtcp2_encode_transport_params( + buf, sizeof(buf), NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, + ¶ms); + + CU_ASSERT((ngtcp2_ssize)len == nwrite); + + rv = ngtcp2_decode_transport_params( + &nparams, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, buf, + (size_t)nwrite); + + CU_ASSERT(0 == rv); + CU_ASSERT(params.initial_max_stream_data_bidi_local == + nparams.initial_max_stream_data_bidi_local); + CU_ASSERT(params.initial_max_stream_data_bidi_remote == + nparams.initial_max_stream_data_bidi_remote); + CU_ASSERT(params.initial_max_stream_data_uni == + nparams.initial_max_stream_data_uni); + CU_ASSERT(params.initial_max_data == nparams.initial_max_data); + CU_ASSERT(params.initial_max_streams_bidi == + nparams.initial_max_streams_bidi); + CU_ASSERT(params.initial_max_streams_uni == nparams.initial_max_streams_uni); + CU_ASSERT(params.max_idle_timeout == nparams.max_idle_timeout); + CU_ASSERT(params.max_udp_payload_size == nparams.max_udp_payload_size); + CU_ASSERT(params.stateless_reset_token_present == + nparams.stateless_reset_token_present); + CU_ASSERT(params.ack_delay_exponent == nparams.ack_delay_exponent); + CU_ASSERT(params.disable_active_migration == + nparams.disable_active_migration); + CU_ASSERT(params.max_ack_delay == nparams.max_ack_delay); + CU_ASSERT(ngtcp2_cid_eq(¶ms.original_dcid, &nparams.original_dcid)); + CU_ASSERT(ngtcp2_cid_eq(¶ms.initial_scid, &nparams.initial_scid)); + CU_ASSERT(params.retry_scid_present == nparams.retry_scid_present); + + memset(¶ms, 0, sizeof(params)); + memset(&nparams, 0, sizeof(nparams)); + + /* CH, all parameters */ + params.initial_max_stream_data_bidi_local = 1000000007; + params.initial_max_stream_data_bidi_remote = 961748941; + params.initial_max_stream_data_uni = 982451653; + params.initial_max_data = 1000000009; + params.initial_max_streams_bidi = 909; + params.initial_max_streams_uni = 911; + params.max_idle_timeout = 1023 * NGTCP2_MILLISECONDS; + params.max_udp_payload_size = 1400; + params.ack_delay_exponent = 20; + params.disable_active_migration = 1; + params.max_ack_delay = 59 * NGTCP2_MILLISECONDS; + params.initial_scid = scid; + params.active_connection_id_limit = 1000000007; + params.max_datagram_frame_size = 65535; + params.grease_quic_bit = 1; + params.version_info.chosen_version = NGTCP2_PROTO_VER_V1; + params.version_info.other_versions = other_versions; + params.version_info.other_versionslen = sizeof(other_versions); + params.version_info_present = 1; + + len = + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + params.initial_max_stream_data_bidi_local) + + varint_paramlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + params.initial_max_stream_data_bidi_remote) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI, + params.initial_max_stream_data_uni) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA, + params.initial_max_data) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI, + params.initial_max_streams_bidi) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI, + params.initial_max_streams_uni) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT, + params.max_idle_timeout / NGTCP2_MILLISECONDS) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE, + params.max_udp_payload_size) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT, + params.ack_delay_exponent) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION) + + ngtcp2_put_uvarintlen(0)) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY, + params.max_ack_delay / NGTCP2_MILLISECONDS) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT, + params.active_connection_id_limit) + + (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.initial_scid.datalen) + + params.initial_scid.datalen) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE, + params.max_datagram_frame_size) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT) + + ngtcp2_put_uvarintlen(0)) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION_DRAFT) + + ngtcp2_put_uvarintlen(sizeof(params.version_info.chosen_version) + + params.version_info.other_versionslen) + + sizeof(params.version_info.chosen_version) + + params.version_info.other_versionslen); + + nwrite = ngtcp2_encode_transport_params( + NULL, 0, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms); + + CU_ASSERT((ngtcp2_ssize)len == nwrite); + + for (i = 0; i < len; ++i) { + nwrite = ngtcp2_encode_transport_params( + buf, i, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms); + CU_ASSERT(NGTCP2_ERR_NOBUF == nwrite); + } + nwrite = ngtcp2_encode_transport_params( + buf, i, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms); + + CU_ASSERT((ngtcp2_ssize)i == nwrite); + + rv = ngtcp2_decode_transport_params( + &nparams, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, buf, (size_t)nwrite); + + CU_ASSERT(0 == rv); + CU_ASSERT(params.initial_max_stream_data_bidi_local == + nparams.initial_max_stream_data_bidi_local); + CU_ASSERT(params.initial_max_stream_data_bidi_remote == + nparams.initial_max_stream_data_bidi_remote); + CU_ASSERT(params.initial_max_stream_data_uni == + nparams.initial_max_stream_data_uni); + CU_ASSERT(params.initial_max_data == nparams.initial_max_data); + CU_ASSERT(params.initial_max_streams_bidi == + nparams.initial_max_streams_bidi); + CU_ASSERT(params.initial_max_streams_uni == nparams.initial_max_streams_uni); + CU_ASSERT(params.max_idle_timeout == nparams.max_idle_timeout); + CU_ASSERT(params.max_udp_payload_size == nparams.max_udp_payload_size); + CU_ASSERT(params.ack_delay_exponent == nparams.ack_delay_exponent); + CU_ASSERT(params.disable_active_migration == + nparams.disable_active_migration); + CU_ASSERT(params.max_ack_delay == nparams.max_ack_delay); + CU_ASSERT(params.active_connection_id_limit == + nparams.active_connection_id_limit); + CU_ASSERT(params.max_datagram_frame_size == nparams.max_datagram_frame_size); + CU_ASSERT(params.grease_quic_bit == nparams.grease_quic_bit); + CU_ASSERT(params.version_info_present == nparams.version_info_present); + CU_ASSERT(params.version_info.chosen_version == + nparams.version_info.chosen_version); + CU_ASSERT(0 == memcmp(params.version_info.other_versions, + nparams.version_info.other_versions, + params.version_info.other_versionslen)); + + memset(¶ms, 0, sizeof(params)); + memset(&nparams, 0, sizeof(nparams)); + + /* EE, all parameters */ + params.initial_max_stream_data_bidi_local = 1000000007; + params.initial_max_stream_data_bidi_remote = 961748941; + params.initial_max_stream_data_uni = 982451653; + params.initial_max_data = 1000000009; + params.initial_max_streams_bidi = 908; + params.initial_max_streams_uni = 16383; + params.max_idle_timeout = 16363 * NGTCP2_MILLISECONDS; + params.max_udp_payload_size = 1200; + params.stateless_reset_token_present = 1; + memset(params.stateless_reset_token, 0xf1, + sizeof(params.stateless_reset_token)); + params.ack_delay_exponent = 20; + params.preferred_address_present = 1; + params.preferred_address.ipv4_present = 0; + sa_in6 = ¶ms.preferred_address.ipv6; + sa_in6->sin6_family = AF_INET6; + memset(&sa_in6->sin6_addr, 0xe1, sizeof(sa_in6->sin6_addr)); + sa_in6->sin6_port = ngtcp2_htons(63111); + params.preferred_address.ipv6_present = 1; + scid_init(¶ms.preferred_address.cid); + memset(params.preferred_address.stateless_reset_token, 0xd1, + sizeof(params.preferred_address.stateless_reset_token)); + params.disable_active_migration = 1; + params.max_ack_delay = 63 * NGTCP2_MILLISECONDS; + params.retry_scid_present = 1; + params.retry_scid = rcid; + params.original_dcid = dcid; + params.initial_scid = scid; + params.active_connection_id_limit = 1073741824; + params.max_datagram_frame_size = 63; + params.grease_quic_bit = 1; + params.version_info.chosen_version = NGTCP2_PROTO_VER_V1; + params.version_info.other_versions = other_versions; + params.version_info.other_versionslen = ngtcp2_arraylen(other_versions); + params.version_info_present = 1; + + len = + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + params.initial_max_stream_data_bidi_local) + + varint_paramlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + params.initial_max_stream_data_bidi_remote) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI, + params.initial_max_stream_data_uni) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA, + params.initial_max_data) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI, + params.initial_max_streams_bidi) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI, + params.initial_max_streams_uni) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT, + params.max_idle_timeout / NGTCP2_MILLISECONDS) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE, + params.max_udp_payload_size) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT, + params.ack_delay_exponent) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION) + + ngtcp2_put_uvarintlen(0)) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY, + params.max_ack_delay / NGTCP2_MILLISECONDS) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT, + params.active_connection_id_limit) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN) + + ngtcp2_put_uvarintlen(NGTCP2_STATELESS_RESET_TOKENLEN) + + NGTCP2_STATELESS_RESET_TOKENLEN) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS) + + ngtcp2_put_uvarintlen(4 + 2 + 16 + 2 + 1 + + params.preferred_address.cid.datalen + + NGTCP2_STATELESS_RESET_TOKENLEN) + + 4 + 2 + 16 + 2 + 1 + params.preferred_address.cid.datalen + + NGTCP2_STATELESS_RESET_TOKENLEN) + + (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.retry_scid.datalen) + + params.retry_scid.datalen) + + (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.original_dcid.datalen) + + params.original_dcid.datalen) + + (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.initial_scid.datalen) + + params.initial_scid.datalen) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE, + params.max_datagram_frame_size) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT) + + ngtcp2_put_uvarintlen(0)) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION_DRAFT) + + ngtcp2_put_uvarintlen(sizeof(params.version_info.chosen_version) + + params.version_info.other_versionslen) + + sizeof(params.version_info.chosen_version) + + params.version_info.other_versionslen); + + nwrite = ngtcp2_encode_transport_params( + NULL, 0, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, ¶ms); + + CU_ASSERT((ngtcp2_ssize)len == nwrite); + + for (i = 0; i < len; ++i) { + nwrite = ngtcp2_encode_transport_params( + buf, i, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, ¶ms); + + CU_ASSERT(NGTCP2_ERR_NOBUF == nwrite); + } + nwrite = ngtcp2_encode_transport_params( + buf, i, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, ¶ms); + + CU_ASSERT((ngtcp2_ssize)i == nwrite); + + rv = ngtcp2_decode_transport_params( + &nparams, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, buf, + (size_t)nwrite); + + CU_ASSERT(0 == rv); + CU_ASSERT(params.initial_max_stream_data_bidi_local == + nparams.initial_max_stream_data_bidi_local); + CU_ASSERT(params.initial_max_stream_data_bidi_remote == + nparams.initial_max_stream_data_bidi_remote); + CU_ASSERT(params.initial_max_stream_data_uni == + nparams.initial_max_stream_data_uni); + CU_ASSERT(params.initial_max_data == nparams.initial_max_data); + CU_ASSERT(params.initial_max_streams_bidi == + nparams.initial_max_streams_bidi); + CU_ASSERT(params.initial_max_streams_uni == nparams.initial_max_streams_uni); + CU_ASSERT(params.max_idle_timeout == nparams.max_idle_timeout); + CU_ASSERT(params.max_udp_payload_size == nparams.max_udp_payload_size); + CU_ASSERT(0 == memcmp(params.stateless_reset_token, + nparams.stateless_reset_token, + sizeof(params.stateless_reset_token))); + CU_ASSERT(params.ack_delay_exponent == nparams.ack_delay_exponent); + CU_ASSERT(params.preferred_address_present == + nparams.preferred_address_present); + CU_ASSERT(0 == memcmp(¶ms.preferred_address.ipv4, + &nparams.preferred_address.ipv4, + sizeof(params.preferred_address.ipv4))); + CU_ASSERT(params.preferred_address.ipv4_present == + nparams.preferred_address.ipv4_present); + CU_ASSERT(0 == memcmp(¶ms.preferred_address.ipv6, + &nparams.preferred_address.ipv6, + sizeof(params.preferred_address.ipv6))); + CU_ASSERT(params.preferred_address.ipv6_present == + nparams.preferred_address.ipv6_present); + CU_ASSERT(ngtcp2_cid_eq(¶ms.preferred_address.cid, + &nparams.preferred_address.cid)); + CU_ASSERT(0 == + memcmp(params.preferred_address.stateless_reset_token, + nparams.preferred_address.stateless_reset_token, + sizeof(params.preferred_address.stateless_reset_token))); + CU_ASSERT(params.disable_active_migration == + nparams.disable_active_migration); + CU_ASSERT(params.max_ack_delay == nparams.max_ack_delay); + CU_ASSERT(params.retry_scid_present == nparams.retry_scid_present); + CU_ASSERT(ngtcp2_cid_eq(¶ms.retry_scid, &nparams.retry_scid)); + CU_ASSERT(ngtcp2_cid_eq(¶ms.initial_scid, &nparams.initial_scid)); + CU_ASSERT(ngtcp2_cid_eq(¶ms.original_dcid, &nparams.original_dcid)); + CU_ASSERT(params.active_connection_id_limit == + nparams.active_connection_id_limit); + CU_ASSERT(params.max_datagram_frame_size == nparams.max_datagram_frame_size); + CU_ASSERT(params.grease_quic_bit = nparams.grease_quic_bit); + CU_ASSERT(params.version_info_present == nparams.version_info_present); + CU_ASSERT(params.version_info.chosen_version == + nparams.version_info.chosen_version); + CU_ASSERT(0 == memcmp(params.version_info.other_versions, + nparams.version_info.other_versions, + params.version_info.other_versionslen)); +} + +void test_ngtcp2_decode_transport_params_new(void) { + ngtcp2_transport_params params, *nparams; + uint8_t buf[512]; + ngtcp2_ssize nwrite; + int rv; + size_t i, len; + ngtcp2_cid rcid, scid, dcid; + uint8_t other_versions[sizeof(uint32_t) * 3]; + ngtcp2_sockaddr_in *sa_in; + + rcid_init(&rcid); + scid_init(&scid); + dcid_init(&dcid); + + memset(¶ms, 0, sizeof(params)); + memset(&nparams, 0, sizeof(nparams)); + + for (i = 0; i < sizeof(other_versions); i += sizeof(uint32_t)) { + ngtcp2_put_uint32be(&other_versions[i], (uint32_t)(0xff000000u + i)); + } + + /* EE, required parameters only */ + params.max_udp_payload_size = NGTCP2_DEFAULT_MAX_RECV_UDP_PAYLOAD_SIZE; + params.ack_delay_exponent = NGTCP2_DEFAULT_ACK_DELAY_EXPONENT; + params.max_ack_delay = NGTCP2_DEFAULT_MAX_ACK_DELAY; + params.original_dcid = dcid; + params.initial_scid = scid; + + len = (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.original_dcid.datalen) + + params.original_dcid.datalen) + + (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.initial_scid.datalen) + + params.initial_scid.datalen); + + nwrite = ngtcp2_encode_transport_params( + buf, sizeof(buf), NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, + ¶ms); + + CU_ASSERT((ngtcp2_ssize)len == nwrite); + + rv = ngtcp2_decode_transport_params_new( + &nparams, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, buf, + (size_t)nwrite, NULL); + + CU_ASSERT(0 == rv); + CU_ASSERT(params.initial_max_stream_data_bidi_local == + nparams->initial_max_stream_data_bidi_local); + CU_ASSERT(params.initial_max_stream_data_bidi_remote == + nparams->initial_max_stream_data_bidi_remote); + CU_ASSERT(params.initial_max_stream_data_uni == + nparams->initial_max_stream_data_uni); + CU_ASSERT(params.initial_max_data == nparams->initial_max_data); + CU_ASSERT(params.initial_max_streams_bidi == + nparams->initial_max_streams_bidi); + CU_ASSERT(params.initial_max_streams_uni == nparams->initial_max_streams_uni); + CU_ASSERT(params.max_idle_timeout == nparams->max_idle_timeout); + CU_ASSERT(params.max_udp_payload_size == nparams->max_udp_payload_size); + CU_ASSERT(params.stateless_reset_token_present == + nparams->stateless_reset_token_present); + CU_ASSERT(params.ack_delay_exponent == nparams->ack_delay_exponent); + CU_ASSERT(params.disable_active_migration == + nparams->disable_active_migration); + CU_ASSERT(params.max_ack_delay == nparams->max_ack_delay); + CU_ASSERT(ngtcp2_cid_eq(¶ms.original_dcid, &nparams->original_dcid)); + CU_ASSERT(ngtcp2_cid_eq(¶ms.initial_scid, &nparams->initial_scid)); + CU_ASSERT(params.retry_scid_present == nparams->retry_scid_present); + + ngtcp2_transport_params_del(nparams, NULL); + memset(¶ms, 0, sizeof(params)); + + /* EE, all parameters */ + params.initial_max_stream_data_bidi_local = 1000000007; + params.initial_max_stream_data_bidi_remote = 961748941; + params.initial_max_stream_data_uni = 982451653; + params.initial_max_data = 1000000009; + params.initial_max_streams_bidi = 908; + params.initial_max_streams_uni = 16383; + params.max_idle_timeout = 16363 * NGTCP2_MILLISECONDS; + params.max_udp_payload_size = 1200; + params.stateless_reset_token_present = 1; + memset(params.stateless_reset_token, 0xf1, + sizeof(params.stateless_reset_token)); + params.ack_delay_exponent = 20; + params.preferred_address_present = 1; + sa_in = ¶ms.preferred_address.ipv4; + sa_in->sin_family = AF_INET; + memset(&sa_in->sin_addr, 0xf1, sizeof(sa_in->sin_addr)); + sa_in->sin_port = ngtcp2_htons(11732); + params.preferred_address.ipv4_present = 1; + params.preferred_address.ipv6_present = 0; + scid_init(¶ms.preferred_address.cid); + memset(params.preferred_address.stateless_reset_token, 0xd1, + sizeof(params.preferred_address.stateless_reset_token)); + params.disable_active_migration = 1; + params.max_ack_delay = 63 * NGTCP2_MILLISECONDS; + params.retry_scid_present = 1; + params.retry_scid = rcid; + params.original_dcid = dcid; + params.initial_scid = scid; + params.active_connection_id_limit = 1073741824; + params.max_datagram_frame_size = 63; + params.grease_quic_bit = 1; + params.version_info.chosen_version = NGTCP2_PROTO_VER_V1; + params.version_info.other_versions = other_versions; + params.version_info.other_versionslen = ngtcp2_arraylen(other_versions); + params.version_info_present = 1; + + len = + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + params.initial_max_stream_data_bidi_local) + + varint_paramlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + params.initial_max_stream_data_bidi_remote) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAM_DATA_UNI, + params.initial_max_stream_data_uni) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_DATA, + params.initial_max_data) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_BIDI, + params.initial_max_streams_bidi) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_INITIAL_MAX_STREAMS_UNI, + params.initial_max_streams_uni) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_IDLE_TIMEOUT, + params.max_idle_timeout / NGTCP2_MILLISECONDS) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_UDP_PAYLOAD_SIZE, + params.max_udp_payload_size) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACK_DELAY_EXPONENT, + params.ack_delay_exponent) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_DISABLE_ACTIVE_MIGRATION) + + ngtcp2_put_uvarintlen(0)) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_ACK_DELAY, + params.max_ack_delay / NGTCP2_MILLISECONDS) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_ACTIVE_CONNECTION_ID_LIMIT, + params.active_connection_id_limit) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_STATELESS_RESET_TOKEN) + + ngtcp2_put_uvarintlen(NGTCP2_STATELESS_RESET_TOKENLEN) + + NGTCP2_STATELESS_RESET_TOKENLEN) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_PREFERRED_ADDRESS) + + ngtcp2_put_uvarintlen(4 + 2 + 16 + 2 + 1 + + params.preferred_address.cid.datalen + + NGTCP2_STATELESS_RESET_TOKENLEN) + + 4 + 2 + 16 + 2 + 1 + params.preferred_address.cid.datalen + + NGTCP2_STATELESS_RESET_TOKENLEN) + + (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_RETRY_SOURCE_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.retry_scid.datalen) + + params.retry_scid.datalen) + + (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_ORIGINAL_DESTINATION_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.original_dcid.datalen) + + params.original_dcid.datalen) + + (ngtcp2_put_uvarintlen( + NGTCP2_TRANSPORT_PARAM_INITIAL_SOURCE_CONNECTION_ID) + + ngtcp2_put_uvarintlen(params.initial_scid.datalen) + + params.initial_scid.datalen) + + varint_paramlen(NGTCP2_TRANSPORT_PARAM_MAX_DATAGRAM_FRAME_SIZE, + params.max_datagram_frame_size) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_GREASE_QUIC_BIT) + + ngtcp2_put_uvarintlen(0)) + + (ngtcp2_put_uvarintlen(NGTCP2_TRANSPORT_PARAM_VERSION_INFORMATION_DRAFT) + + ngtcp2_put_uvarintlen(sizeof(params.version_info.chosen_version) + + params.version_info.other_versionslen) + + sizeof(params.version_info.chosen_version) + + params.version_info.other_versionslen); + + nwrite = ngtcp2_encode_transport_params( + buf, sizeof(buf), NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, + ¶ms); + + CU_ASSERT((ngtcp2_ssize)len == nwrite); + + rv = ngtcp2_decode_transport_params_new( + &nparams, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, buf, + (size_t)nwrite, NULL); + + CU_ASSERT(0 == rv); + CU_ASSERT(params.initial_max_stream_data_bidi_local == + nparams->initial_max_stream_data_bidi_local); + CU_ASSERT(params.initial_max_stream_data_bidi_remote == + nparams->initial_max_stream_data_bidi_remote); + CU_ASSERT(params.initial_max_stream_data_uni == + nparams->initial_max_stream_data_uni); + CU_ASSERT(params.initial_max_data == nparams->initial_max_data); + CU_ASSERT(params.initial_max_streams_bidi == + nparams->initial_max_streams_bidi); + CU_ASSERT(params.initial_max_streams_uni == nparams->initial_max_streams_uni); + CU_ASSERT(params.max_idle_timeout == nparams->max_idle_timeout); + CU_ASSERT(params.max_udp_payload_size == nparams->max_udp_payload_size); + CU_ASSERT(0 == memcmp(params.stateless_reset_token, + nparams->stateless_reset_token, + sizeof(params.stateless_reset_token))); + CU_ASSERT(params.ack_delay_exponent == nparams->ack_delay_exponent); + CU_ASSERT(params.preferred_address_present == + nparams->preferred_address_present); + CU_ASSERT(0 == memcmp(¶ms.preferred_address.ipv4, + &nparams->preferred_address.ipv4, + sizeof(params.preferred_address.ipv4))); + CU_ASSERT(params.preferred_address.ipv4_present == + nparams->preferred_address.ipv4_present); + CU_ASSERT(0 == memcmp(¶ms.preferred_address.ipv6, + &nparams->preferred_address.ipv6, + sizeof(params.preferred_address.ipv6))); + CU_ASSERT(params.preferred_address.ipv6_present == + nparams->preferred_address.ipv6_present); + CU_ASSERT(ngtcp2_cid_eq(¶ms.preferred_address.cid, + &nparams->preferred_address.cid)); + CU_ASSERT(0 == + memcmp(params.preferred_address.stateless_reset_token, + nparams->preferred_address.stateless_reset_token, + sizeof(params.preferred_address.stateless_reset_token))); + CU_ASSERT(params.disable_active_migration == + nparams->disable_active_migration); + CU_ASSERT(params.max_ack_delay == nparams->max_ack_delay); + CU_ASSERT(params.retry_scid_present == nparams->retry_scid_present); + CU_ASSERT(ngtcp2_cid_eq(¶ms.retry_scid, &nparams->retry_scid)); + CU_ASSERT(ngtcp2_cid_eq(¶ms.initial_scid, &nparams->initial_scid)); + CU_ASSERT(ngtcp2_cid_eq(¶ms.original_dcid, &nparams->original_dcid)); + CU_ASSERT(params.active_connection_id_limit == + nparams->active_connection_id_limit); + CU_ASSERT(params.max_datagram_frame_size == nparams->max_datagram_frame_size); + CU_ASSERT(params.grease_quic_bit = nparams->grease_quic_bit); + CU_ASSERT(params.version_info_present == nparams->version_info_present); + CU_ASSERT(params.version_info.chosen_version == + nparams->version_info.chosen_version); + CU_ASSERT(0 == memcmp(params.version_info.other_versions, + nparams->version_info.other_versions, + params.version_info.other_versionslen)); + + ngtcp2_transport_params_del(nparams, NULL); +} diff --git a/tests/ngtcp2_crypto_test.h b/tests/ngtcp2_crypto_test.h new file mode 100644 index 0000000..e57c4bc --- /dev/null +++ b/tests/ngtcp2_crypto_test.h @@ -0,0 +1,35 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_CRYPTO_TEST_H +#define NGTCP2_CRYPTO_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_encode_transport_params(void); +void test_ngtcp2_decode_transport_params_new(void); + +#endif /* NGTCP2_CRYPTO_TEST_H */ diff --git a/tests/ngtcp2_gaptr_test.c b/tests/ngtcp2_gaptr_test.c new file mode 100644 index 0000000..5612b56 --- /dev/null +++ b/tests/ngtcp2_gaptr_test.c @@ -0,0 +1,127 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_gaptr_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_gaptr.h" +#include "ngtcp2_test_helper.h" +#include "ngtcp2_mem.h" + +void test_ngtcp2_gaptr_push(void) { + ngtcp2_gaptr gaptr; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_ksl_it it; + ngtcp2_range r; + int rv; + size_t i; + + ngtcp2_gaptr_init(&gaptr, mem); + + rv = ngtcp2_gaptr_push(&gaptr, 0, 1); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&gaptr.gap); + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(1 == r.begin); + CU_ASSERT(UINT64_MAX == r.end); + + rv = ngtcp2_gaptr_push(&gaptr, 12389, 133); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&gaptr.gap); + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(1 == r.begin); + CU_ASSERT(12389 == r.end); + + ngtcp2_ksl_it_next(&it); + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(12389 + 133 == r.begin); + CU_ASSERT(UINT64_MAX == r.end); + + for (i = 0; i < 2; ++i) { + rv = ngtcp2_gaptr_push(&gaptr, 1, 12389); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&gaptr.gap); + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(12389 + 133 == r.begin); + CU_ASSERT(UINT64_MAX == r.end); + } + + rv = ngtcp2_gaptr_push(&gaptr, 12389 + 133 - 1, 2); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&gaptr.gap); + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(12389 + 133 + 1 == r.begin); + CU_ASSERT(UINT64_MAX == r.end); + + ngtcp2_gaptr_free(&gaptr); +} + +void test_ngtcp2_gaptr_is_pushed(void) { + ngtcp2_gaptr gaptr; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + int rv; + + ngtcp2_gaptr_init(&gaptr, mem); + + rv = ngtcp2_gaptr_push(&gaptr, 1000000007, 1009); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_gaptr_is_pushed(&gaptr, 1000000007, 1009)); + CU_ASSERT(!ngtcp2_gaptr_is_pushed(&gaptr, 1000000007, 1010)); + + ngtcp2_gaptr_free(&gaptr); +} + +void test_ngtcp2_gaptr_drop_first_gap(void) { + ngtcp2_gaptr gaptr; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + int rv; + + ngtcp2_gaptr_init(&gaptr, mem); + + rv = ngtcp2_gaptr_push(&gaptr, 113245, 12); + + CU_ASSERT(0 == rv); + + ngtcp2_gaptr_drop_first_gap(&gaptr); + + CU_ASSERT(ngtcp2_gaptr_is_pushed(&gaptr, 0, 1)); + CU_ASSERT(113245 + 12 == ngtcp2_gaptr_first_gap_offset(&gaptr)); + + ngtcp2_gaptr_free(&gaptr); +} diff --git a/tests/ngtcp2_gaptr_test.h b/tests/ngtcp2_gaptr_test.h new file mode 100644 index 0000000..cc6507c --- /dev/null +++ b/tests/ngtcp2_gaptr_test.h @@ -0,0 +1,36 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_GAPTR_TEST_H +#define NGTCP2_GAPTR_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_gaptr_push(void); +void test_ngtcp2_gaptr_is_pushed(void); +void test_ngtcp2_gaptr_drop_first_gap(void); + +#endif /* NGTCP2_GAPTR_TEST_H */ diff --git a/tests/ngtcp2_idtr_test.c b/tests/ngtcp2_idtr_test.c new file mode 100644 index 0000000..90d15ba --- /dev/null +++ b/tests/ngtcp2_idtr_test.c @@ -0,0 +1,79 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_idtr_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_idtr.h" +#include "ngtcp2_test_helper.h" +#include "ngtcp2_mem.h" + +static int64_t stream_id_from_id(uint64_t id) { return (int64_t)(id * 4); } + +void test_ngtcp2_idtr_open(void) { + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_idtr idtr; + int rv; + ngtcp2_ksl_it it; + ngtcp2_range key; + + ngtcp2_idtr_init(&idtr, 0, mem); + + rv = ngtcp2_idtr_open(&idtr, stream_id_from_id(0)); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&idtr.gap.gap); + key = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(1 == key.begin); + CU_ASSERT(UINT64_MAX == key.end); + + rv = ngtcp2_idtr_open(&idtr, stream_id_from_id(1000000007)); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&idtr.gap.gap); + key = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(1 == key.begin); + CU_ASSERT(1000000007 == key.end); + + ngtcp2_ksl_it_next(&it); + key = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(1000000008 == key.begin); + CU_ASSERT(UINT64_MAX == key.end); + + rv = ngtcp2_idtr_open(&idtr, stream_id_from_id(0)); + + CU_ASSERT(NGTCP2_ERR_STREAM_IN_USE == rv); + + rv = ngtcp2_idtr_open(&idtr, stream_id_from_id(1000000007)); + + CU_ASSERT(NGTCP2_ERR_STREAM_IN_USE == rv); + + ngtcp2_idtr_free(&idtr); +} diff --git a/tests/ngtcp2_idtr_test.h b/tests/ngtcp2_idtr_test.h new file mode 100644 index 0000000..ebba934 --- /dev/null +++ b/tests/ngtcp2_idtr_test.h @@ -0,0 +1,34 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_IDTR_TEST_H +#define NGTCP2_IDTR_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_idtr_open(void); + +#endif /* NGTCP2_IDTR_TEST_H */ diff --git a/tests/ngtcp2_ksl_test.c b/tests/ngtcp2_ksl_test.c new file mode 100644 index 0000000..54bdd1b --- /dev/null +++ b/tests/ngtcp2_ksl_test.c @@ -0,0 +1,502 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_ksl_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_ksl.h" +#include "ngtcp2_test_helper.h" + +static int less(const ngtcp2_ksl_key *lhs, const ngtcp2_ksl_key *rhs) { + return *(int64_t *)lhs < *(int64_t *)rhs; +} + +void test_ngtcp2_ksl_insert(void) { + static const int64_t keys[] = {10, 3, 8, 11, 16, 12, 1, 5, 4, + 0, 13, 7, 9, 2, 14, 6, 15}; + ngtcp2_ksl ksl; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + size_t i; + ngtcp2_ksl_it it; + int64_t k; + + ngtcp2_ksl_init(&ksl, less, sizeof(int64_t), mem); + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &keys[i], NULL)); + it = ngtcp2_ksl_lower_bound(&ksl, &keys[i]); + + CU_ASSERT(keys[i] == *(int64_t *)ngtcp2_ksl_it_key(&it)); + } + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &keys[i])); + it = ngtcp2_ksl_lower_bound(&ksl, &keys[i]); + if (!ngtcp2_ksl_it_end(&it)) { + CU_ASSERT(keys[i] < *(int64_t *)ngtcp2_ksl_it_key(&it)); + } + } + + ngtcp2_ksl_free(&ksl); + + /* check the case that the right end range is removed */ + ngtcp2_ksl_init(&ksl, less, sizeof(int64_t), mem); + + for (i = 0; i < 32; ++i) { + k = (int64_t)i; + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &k, NULL)); + } + + /* Removing 15 which is the last node in a blk. */ + k = 15; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, &it, &k)); + + CU_ASSERT(16 == *(int64_t *)ngtcp2_ksl_it_key(&it)); + + /* Insert 15 again works */ + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &k, NULL)); + + k = 15; + it = ngtcp2_ksl_lower_bound(&ksl, &k); + + CU_ASSERT(15 == *(int64_t *)ngtcp2_ksl_it_key(&it)); + + ngtcp2_ksl_free(&ksl); + + /* Check the case that the intermediate node contains smaller key + than ancestor node. Make sure that inserting key larger than + that still works.*/ + ngtcp2_ksl_init(&ksl, less, sizeof(int64_t), mem); + + for (i = 0; i < 760; ++i) { + k = (int64_t)i; + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &k, NULL)); + } + + k = 255; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + k = 254; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + k = 253; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + + k = 253; + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &k, NULL)); + it = ngtcp2_ksl_lower_bound(&ksl, &k); + + CU_ASSERT(253 == *(int64_t *)ngtcp2_ksl_it_key(&it)); + + ngtcp2_ksl_free(&ksl); + + /* check merge node (head) */ + ngtcp2_ksl_init(&ksl, less, sizeof(int64_t), mem); + + for (i = 0; i < 32; ++i) { + k = (int64_t)i; + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &k, NULL)); + } + + /* Removing these 3 nodes kicks merging 2 nodes under head */ + k = 15; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + k = 14; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + k = 13; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + + CU_ASSERT(29 == ksl.head->n); + + ngtcp2_ksl_free(&ksl); + + /* check merge node (non head) */ + ngtcp2_ksl_init(&ksl, less, sizeof(int64_t), mem); + + for (i = 0; i < 32 + 18; ++i) { + k = (int64_t)i; + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &k, NULL)); + } + + /* Removing these 3 nodes kicks merging 2 nodes */ + k = 15; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + k = 14; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + k = 13; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + + CU_ASSERT(2 == ksl.head->n); + CU_ASSERT(29 == ngtcp2_ksl_nth_node(&ksl, ksl.head, 0)->blk->n); + CU_ASSERT(18 == ngtcp2_ksl_nth_node(&ksl, ksl.head, 1)->blk->n); + + ngtcp2_ksl_free(&ksl); + + /* Iterate backwards */ + ngtcp2_ksl_init(&ksl, less, sizeof(int64_t), mem); + + /* split nodes */ + for (i = 0; i < 100; ++i) { + k = (int64_t)i; + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &k, NULL)); + } + + /* merge nodes */ + for (i = 0; i < 50; ++i) { + k = (int64_t)i; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + } + + i = 99; + for (it = ngtcp2_ksl_end(&ksl); !ngtcp2_ksl_it_begin(&it);) { + ngtcp2_ksl_it_prev(&it); + + CU_ASSERT((int64_t)i-- == *(int64_t *)ngtcp2_ksl_it_key(&it)); + } + + /* head only */ + for (i = 50; i < 88; ++i) { + k = (int64_t)i; + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &k)); + } + + i = 99; + for (it = ngtcp2_ksl_end(&ksl); !ngtcp2_ksl_it_begin(&it);) { + ngtcp2_ksl_it_prev(&it); + + CU_ASSERT((int64_t)i-- == *(int64_t *)ngtcp2_ksl_it_key(&it)); + } + + ngtcp2_ksl_free(&ksl); +} + +void test_ngtcp2_ksl_clear(void) { + ngtcp2_ksl ksl; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_ksl_it it; + size_t i; + int64_t k; + + ngtcp2_ksl_init(&ksl, less, sizeof(int64_t), mem); + + for (i = 0; i < 100; ++i) { + k = (int64_t)i; + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &k, NULL)); + } + + ngtcp2_ksl_clear(&ksl); + + CU_ASSERT(0 == ngtcp2_ksl_len(&ksl)); + + it = ngtcp2_ksl_begin(&ksl); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + it = ngtcp2_ksl_end(&ksl); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_ksl_free(&ksl); +} + +void test_ngtcp2_ksl_range(void) { + static const ngtcp2_range keys[] = { + {10, 11}, {3, 4}, {8, 9}, {11, 12}, {16, 17}, {12, 13}, + {1, 2}, {5, 6}, {4, 5}, {0, 1}, {13, 14}, {7, 8}, + {9, 10}, {2, 3}, {14, 15}, {6, 7}, {15, 16}, {17, 18}, + {18, 19}, {19, 20}, {20, 21}, {202, 203}, {203, 204}, {204, 205}, + {205, 206}, {206, 207}, {207, 208}, {208, 209}, {209, 210}, {210, 211}, + {211, 212}, {212, 213}, {213, 214}, {214, 215}, {215, 216}}; + ngtcp2_ksl ksl; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + size_t i; + ngtcp2_range r; + ngtcp2_ksl_it it; + ngtcp2_ksl_node *node; + + ngtcp2_ksl_init(&ksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), mem); + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &keys[i], NULL)); + it = ngtcp2_ksl_lower_bound_compar(&ksl, &keys[i], + ngtcp2_ksl_range_exclusive_compar); + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(ngtcp2_range_eq(&keys[i], &r)); + } + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &keys[i])); + it = ngtcp2_ksl_lower_bound_compar(&ksl, &keys[i], + ngtcp2_ksl_range_exclusive_compar); + + if (!ngtcp2_ksl_it_end(&it)) { + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(keys[i].end <= r.begin); + } + } + + ngtcp2_ksl_free(&ksl); + + /* check merge node (head) */ + ngtcp2_ksl_init(&ksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), mem); + + for (i = 0; i < 32; ++i) { + ngtcp2_range_init(&r, i, i + 1); + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &r, NULL)); + } + + /* Removing these 3 nodes kicks merging 2 nodes under head */ + ngtcp2_range_init(&r, 13, 14); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + + ngtcp2_range_init(&r, 14, 15); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + + ngtcp2_range_init(&r, 15, 16); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + + CU_ASSERT(29 == ksl.head->n); + + ngtcp2_ksl_free(&ksl); + + /* check merge node (non head) */ + ngtcp2_ksl_init(&ksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), mem); + + for (i = 0; i < 32 + 18; ++i) { + ngtcp2_range_init(&r, i, i + 1); + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &r, NULL)); + } + + /* Removing these 3 nodes kicks merging 2 nodes */ + ngtcp2_range_init(&r, 13, 14); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + + ngtcp2_range_init(&r, 14, 15); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + + ngtcp2_range_init(&r, 15, 16); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + + CU_ASSERT(2 == ksl.head->n); + CU_ASSERT(29 == ngtcp2_ksl_nth_node(&ksl, ksl.head, 0)->blk->n); + CU_ASSERT(18 == ngtcp2_ksl_nth_node(&ksl, ksl.head, 1)->blk->n); + + ngtcp2_ksl_free(&ksl); + + /* shift_left */ + ngtcp2_ksl_init(&ksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), mem); + + for (i = 1; i < 6400; i += 100) { + ngtcp2_range_init(&r, i, i + 1); + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &r, NULL)); + } + + ngtcp2_range_init(&r, 1501, 1502); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + ngtcp2_range_init(&r, 1401, 1402); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + + r = *(ngtcp2_range *)(void *)ngtcp2_ksl_nth_node( + &ksl, ngtcp2_ksl_nth_node(&ksl, ksl.head, 1)->blk, 0) + ->key; + + CU_ASSERT(1701 == r.begin); + + ngtcp2_ksl_free(&ksl); + + /* shift_right */ + ngtcp2_ksl_init(&ksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), mem); + + for (i = 0; i < 32; ++i) { + ngtcp2_range_init(&r, i, i + 1); + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &r, NULL)); + } + + ngtcp2_range_init(&r, 17, 18); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + ngtcp2_range_init(&r, 16, 17); + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &r)); + + node = ngtcp2_ksl_nth_node(&ksl, ksl.head, 0); + r = *(ngtcp2_range *)(void *)ngtcp2_ksl_nth_node(&ksl, node->blk, + node->blk->n - 1) + ->key; + + CU_ASSERT(14 == r.begin); + + ngtcp2_ksl_free(&ksl); +} + +void test_ngtcp2_ksl_update_key_range(void) { + static ngtcp2_range ranges[] = { + {0, 5}, {10, 15}, {20, 25}, {30, 35}, {40, 45}, {50, 55}, + {60, 65}, {70, 75}, {80, 85}, {90, 95}, {100, 105}, {110, 115}, + {120, 125}, {130, 135}, {140, 145}, {150, 155}, {160, 165}, {170, 175}}; + ngtcp2_ksl ksl; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + size_t i; + ngtcp2_range r; + ngtcp2_ksl_it it; + + ngtcp2_ksl_init(&ksl, ngtcp2_ksl_range_compar, sizeof(ngtcp2_range), mem); + + for (i = 0; i < ngtcp2_arraylen(ranges); ++i) { + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &ranges[i], NULL)); + } + + r.begin = 70; + r.end = 72; + ngtcp2_ksl_update_key(&ksl, &ranges[7], &r); + + r.begin = 73; + r.end = 74; + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &r, NULL)); + + r.begin = 74; + r.end = 75; + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &r, NULL)); + + r.begin = 74; + r.end = 75; + it = ngtcp2_ksl_lower_bound_compar(&ksl, &r, + ngtcp2_ksl_range_exclusive_compar); + + r = *(ngtcp2_range *)ngtcp2_ksl_it_key(&it); + + CU_ASSERT(r.begin == 74); + CU_ASSERT(r.end == 75); + + ngtcp2_ksl_free(&ksl); +} + +static void shuffle(int64_t *a, size_t n) { + size_t i, j; + int64_t t; + + for (i = n - 1; i >= 1; --i) { + j = (size_t)((double)(i + 1) * rand() / (RAND_MAX + 1.0)); + t = a[j]; + a[j] = a[i]; + a[i] = t; + } +} + +void test_ngtcp2_ksl_dup(void) { + static int64_t keys[16000]; + size_t i, j; + ngtcp2_ksl ksl; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_ksl_it it; + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + keys[i] = (int64_t)i; + } + + for (j = 0; j < 10; ++j) { + ngtcp2_ksl_init(&ksl, less, sizeof(int64_t), mem); + + shuffle(keys, ngtcp2_arraylen(keys)); + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &keys[i], NULL)); + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == + ngtcp2_ksl_insert(&ksl, NULL, &keys[i], NULL)); + + it = ngtcp2_ksl_lower_bound(&ksl, &keys[i]); + + CU_ASSERT(keys[i] == *(int64_t *)ngtcp2_ksl_it_key(&it)); + } + + shuffle(keys, ngtcp2_arraylen(keys)); + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + CU_ASSERT(0 == ngtcp2_ksl_remove(&ksl, NULL, &keys[i])); + + it = ngtcp2_ksl_begin(&ksl); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == + ngtcp2_ksl_remove(&ksl, &it, &keys[i])); + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + it = ngtcp2_ksl_lower_bound(&ksl, &keys[i]); + + CU_ASSERT(ngtcp2_ksl_it_end(&it) || + keys[i] < *(int64_t *)ngtcp2_ksl_it_key(&it)); + + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &keys[i], NULL)); + + it = ngtcp2_ksl_begin(&ksl); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == + ngtcp2_ksl_insert(&ksl, &it, &keys[i], NULL)); + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + it = ngtcp2_ksl_lower_bound(&ksl, &keys[i]); + CU_ASSERT(keys[i] == *(int64_t *)ngtcp2_ksl_it_key(&it)); + } + + ngtcp2_ksl_free(&ksl); + } +} + +void test_ngtcp2_ksl_remove_hint(void) { + static int64_t keys[16000]; + ngtcp2_ksl ksl; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_ksl_it it; + size_t i, j; + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + keys[i] = (int64_t)i; + } + + for (j = 0; j < 10; ++j) { + ngtcp2_ksl_init(&ksl, less, sizeof(int64_t), mem); + + shuffle(keys, ngtcp2_arraylen(keys)); + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + CU_ASSERT(0 == ngtcp2_ksl_insert(&ksl, NULL, &keys[i], NULL)); + } + + shuffle(keys, ngtcp2_arraylen(keys)); + + for (i = 0; i < ngtcp2_arraylen(keys); ++i) { + it = ngtcp2_ksl_lower_bound(&ksl, &keys[i]); + + CU_ASSERT(!ngtcp2_ksl_it_end(&it)); + CU_ASSERT(keys[i] == *(int64_t *)ngtcp2_ksl_it_key(&it)); + CU_ASSERT(0 == ngtcp2_ksl_remove_hint(&ksl, &it, &it, &keys[i])); + + it = ngtcp2_ksl_lower_bound(&ksl, &keys[i]); + + CU_ASSERT(ngtcp2_ksl_it_end(&it) || + keys[i] != *(int64_t *)ngtcp2_ksl_it_key(&it)); + CU_ASSERT(ngtcp2_arraylen(keys) - i - 1 == ngtcp2_ksl_len(&ksl)); + } + + ngtcp2_ksl_free(&ksl); + } +} diff --git a/tests/ngtcp2_ksl_test.h b/tests/ngtcp2_ksl_test.h new file mode 100644 index 0000000..9cdc4d4 --- /dev/null +++ b/tests/ngtcp2_ksl_test.h @@ -0,0 +1,39 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_KSL_TEST_H +#define NGTCP2_KSL_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_ksl_insert(void); +void test_ngtcp2_ksl_clear(void); +void test_ngtcp2_ksl_range(void); +void test_ngtcp2_ksl_update_key_range(void); +void test_ngtcp2_ksl_dup(void); +void test_ngtcp2_ksl_remove_hint(void); + +#endif /* NGTCP2_KSL_TEST_H */ diff --git a/tests/ngtcp2_map_test.c b/tests/ngtcp2_map_test.c new file mode 100644 index 0000000..8ee4086 --- /dev/null +++ b/tests/ngtcp2_map_test.c @@ -0,0 +1,206 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_map_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_map.h" + +typedef struct strentry { + ngtcp2_map_key_type key; + const char *str; +} strentry; + +static void strentry_init(strentry *entry, ngtcp2_map_key_type key, + const char *str) { + entry->key = key; + entry->str = str; +} + +void test_ngtcp2_map(void) { + strentry foo, FOO, bar, baz, shrubbery; + ngtcp2_map map; + ngtcp2_map_init(&map, ngtcp2_mem_default()); + + strentry_init(&foo, 1, "foo"); + strentry_init(&FOO, 1, "FOO"); + strentry_init(&bar, 2, "bar"); + strentry_init(&baz, 3, "baz"); + strentry_init(&shrubbery, 4, "shrubbery"); + + CU_ASSERT(0 == ngtcp2_map_insert(&map, foo.key, &foo)); + CU_ASSERT(strcmp("foo", ((strentry *)ngtcp2_map_find(&map, 1))->str) == 0); + CU_ASSERT(1 == ngtcp2_map_size(&map)); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == + ngtcp2_map_insert(&map, FOO.key, &FOO)); + + CU_ASSERT(1 == ngtcp2_map_size(&map)); + CU_ASSERT(strcmp("foo", ((strentry *)ngtcp2_map_find(&map, 1))->str) == 0); + + CU_ASSERT(0 == ngtcp2_map_insert(&map, bar.key, &bar)); + CU_ASSERT(2 == ngtcp2_map_size(&map)); + + CU_ASSERT(0 == ngtcp2_map_insert(&map, baz.key, &baz)); + CU_ASSERT(3 == ngtcp2_map_size(&map)); + + CU_ASSERT(0 == ngtcp2_map_insert(&map, shrubbery.key, &shrubbery)); + CU_ASSERT(4 == ngtcp2_map_size(&map)); + + CU_ASSERT(strcmp("baz", ((strentry *)ngtcp2_map_find(&map, 3))->str) == 0); + + ngtcp2_map_remove(&map, 3); + CU_ASSERT(3 == ngtcp2_map_size(&map)); + CU_ASSERT(NULL == ngtcp2_map_find(&map, 3)); + + ngtcp2_map_remove(&map, 1); + CU_ASSERT(2 == ngtcp2_map_size(&map)); + CU_ASSERT(NULL == ngtcp2_map_find(&map, 1)); + + /* Erasing non-existent entry */ + ngtcp2_map_remove(&map, 1); + CU_ASSERT(2 == ngtcp2_map_size(&map)); + CU_ASSERT(NULL == ngtcp2_map_find(&map, 1)); + + CU_ASSERT(strcmp("bar", ((strentry *)ngtcp2_map_find(&map, 2))->str) == 0); + CU_ASSERT(strcmp("shrubbery", ((strentry *)ngtcp2_map_find(&map, 4))->str) == + 0); + + ngtcp2_map_free(&map); +} + +static void shuffle(int *a, int n) { + int i; + for (i = n - 1; i >= 1; --i) { + size_t j = (size_t)((double)(i + 1) * rand() / (RAND_MAX + 1.0)); + int t = a[j]; + a[j] = a[i]; + a[i] = t; + } +} + +static int eachfun(void *data, void *ptr) { + (void)data; + (void)ptr; + + return 0; +} + +#define NUM_ENT 6000 +static strentry arr[NUM_ENT]; +static int order[NUM_ENT]; + +void test_ngtcp2_map_functional(void) { + ngtcp2_map map; + int i; + strentry *ent; + + ngtcp2_map_init(&map, ngtcp2_mem_default()); + for (i = 0; i < NUM_ENT; ++i) { + strentry_init(&arr[i], (ngtcp2_map_key_type)(i + 1), "foo"); + order[i] = i + 1; + } + /* insertion */ + shuffle(order, NUM_ENT); + for (i = 0; i < NUM_ENT; ++i) { + ent = &arr[order[i] - 1]; + CU_ASSERT(0 == ngtcp2_map_insert(&map, ent->key, ent)); + } + + CU_ASSERT(NUM_ENT == ngtcp2_map_size(&map)); + + /* traverse */ + ngtcp2_map_each(&map, eachfun, NULL); + /* find */ + shuffle(order, NUM_ENT); + for (i = 0; i < NUM_ENT; ++i) { + CU_ASSERT(NULL != ngtcp2_map_find(&map, (ngtcp2_map_key_type)order[i])); + } + /* remove */ + for (i = 0; i < NUM_ENT; ++i) { + CU_ASSERT(0 == ngtcp2_map_remove(&map, (ngtcp2_map_key_type)order[i])); + } + + /* each_free (but no op function for testing purpose) */ + for (i = 0; i < NUM_ENT; ++i) { + strentry_init(&arr[i], (ngtcp2_map_key_type)(i + 1), "foo"); + } + /* insert once again */ + for (i = 0; i < NUM_ENT; ++i) { + ent = &arr[i]; + CU_ASSERT(0 == ngtcp2_map_insert(&map, ent->key, ent)); + } + ngtcp2_map_each_free(&map, eachfun, NULL); + ngtcp2_map_free(&map); +} + +static int entry_free(void *data, void *ptr) { + const ngtcp2_mem *mem = ptr; + + mem->free(data, NULL); + return 0; +} + +void test_ngtcp2_map_each_free(void) { + const ngtcp2_mem *mem = ngtcp2_mem_default(); + strentry *foo = mem->malloc(sizeof(strentry), NULL), + *bar = mem->malloc(sizeof(strentry), NULL), + *baz = mem->malloc(sizeof(strentry), NULL), + *shrubbery = mem->malloc(sizeof(strentry), NULL); + ngtcp2_map map; + ngtcp2_map_init(&map, ngtcp2_mem_default()); + + strentry_init(foo, 1, "foo"); + strentry_init(bar, 2, "bar"); + strentry_init(baz, 3, "baz"); + strentry_init(shrubbery, 4, "shrubbery"); + + ngtcp2_map_insert(&map, foo->key, foo); + ngtcp2_map_insert(&map, bar->key, bar); + ngtcp2_map_insert(&map, baz->key, baz); + ngtcp2_map_insert(&map, shrubbery->key, shrubbery); + + ngtcp2_map_each_free(&map, entry_free, (void *)mem); + ngtcp2_map_free(&map); +} + +void test_ngtcp2_map_clear(void) { + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_map map; + strentry foo; + + strentry_init(&foo, 1, "foo"); + + ngtcp2_map_init(&map, mem); + + CU_ASSERT(0 == ngtcp2_map_insert(&map, foo.key, &foo)); + + ngtcp2_map_clear(&map); + + CU_ASSERT(0 == ngtcp2_map_size(&map)); + + ngtcp2_map_free(&map); +} diff --git a/tests/ngtcp2_map_test.h b/tests/ngtcp2_map_test.h new file mode 100644 index 0000000..c8dd3eb --- /dev/null +++ b/tests/ngtcp2_map_test.h @@ -0,0 +1,38 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2012 nghttp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_MAP_TEST_H +#define NGTCP2_MAP_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_map(void); +void test_ngtcp2_map_functional(void); +void test_ngtcp2_map_each_free(void); +void test_ngtcp2_map_clear(void); + +#endif /* NGTCP2_MAP_TEST_H */ diff --git a/tests/ngtcp2_pkt_test.c b/tests/ngtcp2_pkt_test.c new file mode 100644 index 0000000..f225610 --- /dev/null +++ b/tests/ngtcp2_pkt_test.c @@ -0,0 +1,1644 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_pkt_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_pkt.h" +#include "ngtcp2_test_helper.h" +#include "ngtcp2_conv.h" +#include "ngtcp2_cid.h" +#include "ngtcp2_str.h" +#include "ngtcp2_vec.h" + +static int null_retry_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + (void)dest; + (void)aead; + (void)aead_ctx; + (void)plaintext; + (void)plaintextlen; + (void)nonce; + (void)noncelen; + (void)aad; + (void)aadlen; + + if (plaintextlen && plaintext != dest) { + memcpy(dest, plaintext, plaintextlen); + } + memset(dest + plaintextlen, 0, NGTCP2_RETRY_TAGLEN); + + return 0; +} + +void test_ngtcp2_pkt_decode_version_cid(void) { + uint8_t buf[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; + ngtcp2_version_cid vc; + int rv; + uint8_t *p; + + /* Supported QUIC version */ + p = buf; + *p++ = NGTCP2_HEADER_FORM_BIT; + p = ngtcp2_put_uint32be(p, NGTCP2_PROTO_VER_V1); + *p++ = NGTCP2_MAX_CIDLEN; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN); + *p++ = NGTCP2_MAX_CIDLEN - 1; + p = ngtcp2_setmem(p, 0xf2, NGTCP2_MAX_CIDLEN - 1); + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, (size_t)(p - buf), 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_PROTO_VER_V1 == vc.version); + CU_ASSERT(NGTCP2_MAX_CIDLEN == vc.dcidlen); + CU_ASSERT(&buf[6] == vc.dcid); + CU_ASSERT(NGTCP2_MAX_CIDLEN - 1 == vc.scidlen); + CU_ASSERT(&buf[6 + NGTCP2_MAX_CIDLEN + 1] == vc.scid); + + /* Unsupported QUIC version */ + memset(buf, 0, sizeof(buf)); + p = buf; + *p++ = NGTCP2_HEADER_FORM_BIT; + p = ngtcp2_put_uint32be(p, 0xffffff00); + *p++ = NGTCP2_MAX_CIDLEN; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN); + *p++ = NGTCP2_MAX_CIDLEN - 1; + ngtcp2_setmem(p, 0xf2, NGTCP2_MAX_CIDLEN - 1); + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, sizeof(buf), 0); + + CU_ASSERT(NGTCP2_ERR_VERSION_NEGOTIATION == rv); + CU_ASSERT(0xffffff00 == vc.version); + CU_ASSERT(NGTCP2_MAX_CIDLEN == vc.dcidlen); + CU_ASSERT(&buf[6] == vc.dcid); + CU_ASSERT(NGTCP2_MAX_CIDLEN - 1 == vc.scidlen); + CU_ASSERT(&buf[6 + NGTCP2_MAX_CIDLEN + 1] == vc.scid); + + /* Unsupported QUIC version with UDP payload size < 1200 */ + p = buf; + *p++ = NGTCP2_HEADER_FORM_BIT; + p = ngtcp2_put_uint32be(p, 0xffffff00); + *p++ = NGTCP2_MAX_CIDLEN; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN); + *p++ = NGTCP2_MAX_CIDLEN - 1; + p = ngtcp2_setmem(p, 0xf2, NGTCP2_MAX_CIDLEN - 1); + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, (size_t)(p - buf), 0); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv); + + /* Supported QUIC version with long CID */ + p = buf; + *p++ = NGTCP2_HEADER_FORM_BIT; + p = ngtcp2_put_uint32be(p, NGTCP2_PROTO_VER_V1); + *p++ = NGTCP2_MAX_CIDLEN + 1; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN + 1); + *p++ = NGTCP2_MAX_CIDLEN; + p = ngtcp2_setmem(p, 0xf2, NGTCP2_MAX_CIDLEN); + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, (size_t)(p - buf), 0); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv); + + /* Unsupported QUIC version with long CID */ + memset(buf, 0, sizeof(buf)); + p = buf; + *p++ = NGTCP2_HEADER_FORM_BIT; + p = ngtcp2_put_uint32be(p, 0xffffff00); + *p++ = NGTCP2_MAX_CIDLEN + 1; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN + 1); + *p++ = NGTCP2_MAX_CIDLEN; + ngtcp2_setmem(p, 0xf2, NGTCP2_MAX_CIDLEN); + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, sizeof(buf), 0); + + CU_ASSERT(NGTCP2_ERR_VERSION_NEGOTIATION == rv); + CU_ASSERT(0xffffff00 == vc.version); + CU_ASSERT(NGTCP2_MAX_CIDLEN + 1 == vc.dcidlen); + CU_ASSERT(&buf[6] == vc.dcid); + CU_ASSERT(NGTCP2_MAX_CIDLEN == vc.scidlen); + CU_ASSERT(&buf[6 + NGTCP2_MAX_CIDLEN + 1 + 1] == vc.scid); + + /* VN */ + p = buf; + *p++ = NGTCP2_HEADER_FORM_BIT; + p = ngtcp2_put_uint32be(p, 0); + *p++ = NGTCP2_MAX_CIDLEN; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN); + *p++ = NGTCP2_MAX_CIDLEN - 1; + p = ngtcp2_setmem(p, 0xf2, NGTCP2_MAX_CIDLEN - 1); + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, (size_t)(p - buf), 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == vc.version); + CU_ASSERT(NGTCP2_MAX_CIDLEN == vc.dcidlen); + CU_ASSERT(&buf[6] == vc.dcid); + CU_ASSERT(NGTCP2_MAX_CIDLEN - 1 == vc.scidlen); + CU_ASSERT(&buf[6 + NGTCP2_MAX_CIDLEN + 1] == vc.scid); + + /* VN with long CID */ + p = buf; + *p++ = NGTCP2_HEADER_FORM_BIT; + p = ngtcp2_put_uint32be(p, 0); + *p++ = NGTCP2_MAX_CIDLEN + 1; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN + 1); + *p++ = NGTCP2_MAX_CIDLEN; + p = ngtcp2_setmem(p, 0xf2, NGTCP2_MAX_CIDLEN); + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, (size_t)(p - buf), 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == vc.version); + CU_ASSERT(NGTCP2_MAX_CIDLEN + 1 == vc.dcidlen); + CU_ASSERT(&buf[6] == vc.dcid); + CU_ASSERT(NGTCP2_MAX_CIDLEN == vc.scidlen); + CU_ASSERT(&buf[6 + NGTCP2_MAX_CIDLEN + 1 + 1] == vc.scid); + + /* Malformed Long packet */ + p = buf; + *p++ = NGTCP2_HEADER_FORM_BIT; + p = ngtcp2_put_uint32be(p, NGTCP2_PROTO_VER_V1); + *p++ = NGTCP2_MAX_CIDLEN; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN); + *p++ = NGTCP2_MAX_CIDLEN - 1; + p = ngtcp2_setmem(p, 0xf2, NGTCP2_MAX_CIDLEN - 1); + --p; + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, (size_t)(p - buf), 0); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv); + + /* Short packet */ + p = buf; + *p++ = 0; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN); + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, (size_t)(p - buf), + NGTCP2_MAX_CIDLEN); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == vc.version); + CU_ASSERT(&buf[1] == vc.dcid); + CU_ASSERT(NGTCP2_MAX_CIDLEN == vc.dcidlen); + CU_ASSERT(NULL == vc.scid); + CU_ASSERT(0 == vc.scidlen); + + /* Malformed Short packet */ + p = buf; + *p++ = 0; + p = ngtcp2_setmem(p, 0xf1, NGTCP2_MAX_CIDLEN); + --p; + + rv = ngtcp2_pkt_decode_version_cid(&vc, buf, (size_t)(p - buf), + NGTCP2_MAX_CIDLEN); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv); +} + +void test_ngtcp2_pkt_decode_hd_long(void) { + ngtcp2_pkt_hd hd, nhd; + uint8_t buf[256]; + ngtcp2_ssize rv; + ngtcp2_cid dcid, scid; + size_t len; + + dcid_init(&dcid); + scid_init(&scid); + + /* Handshake */ + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_HANDSHAKE, + &dcid, &scid, 0xe1e2e3e4u, 4, NGTCP2_PROTO_VER_V1, 16383); + + rv = ngtcp2_pkt_encode_hd_long(buf, sizeof(buf), &hd); + + len = 1 + 4 + 1 + dcid.datalen + 1 + scid.datalen + NGTCP2_PKT_LENGTHLEN + 4; + + CU_ASSERT((ngtcp2_ssize)len == rv); + CU_ASSERT(buf[0] & NGTCP2_FIXED_BIT_MASK); + + rv = pkt_decode_hd_long(&nhd, buf, len); + + CU_ASSERT((ngtcp2_ssize)len == rv); + CU_ASSERT(hd.type == nhd.type); + CU_ASSERT(hd.flags == nhd.flags); + CU_ASSERT(ngtcp2_cid_eq(&hd.dcid, &nhd.dcid)); + CU_ASSERT(ngtcp2_cid_eq(&hd.scid, &nhd.scid)); + CU_ASSERT(0xe1e2e3e4u == nhd.pkt_num); + CU_ASSERT(hd.version == nhd.version); + CU_ASSERT(hd.len == nhd.len); + + /* Handshake without Fixed Bit set */ + ngtcp2_pkt_hd_init( + &hd, NGTCP2_PKT_FLAG_LONG_FORM | NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR, + NGTCP2_PKT_HANDSHAKE, &dcid, &scid, 0xe1e2e3e4u, 4, NGTCP2_PROTO_VER_V1, + 16383); + + rv = ngtcp2_pkt_encode_hd_long(buf, sizeof(buf), &hd); + + len = 1 + 4 + 1 + dcid.datalen + 1 + scid.datalen + NGTCP2_PKT_LENGTHLEN + 4; + + CU_ASSERT((ngtcp2_ssize)len == rv); + CU_ASSERT((buf[0] & NGTCP2_FIXED_BIT_MASK) == 0); + + rv = pkt_decode_hd_long(&nhd, buf, len); + + CU_ASSERT((ngtcp2_ssize)len == rv); + CU_ASSERT(hd.type == nhd.type); + CU_ASSERT(hd.flags == nhd.flags); + CU_ASSERT(ngtcp2_cid_eq(&hd.dcid, &nhd.dcid)); + CU_ASSERT(ngtcp2_cid_eq(&hd.scid, &nhd.scid)); + CU_ASSERT(0xe1e2e3e4u == nhd.pkt_num); + CU_ASSERT(hd.version == nhd.version); + CU_ASSERT(hd.len == nhd.len); + + /* VN */ + /* Set random packet type */ + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_LONG_FORM, NGTCP2_PKT_HANDSHAKE, + &dcid, &scid, 0, 4, NGTCP2_PROTO_VER_V1, 0); + + rv = ngtcp2_pkt_encode_hd_long(buf, sizeof(buf), &hd); + /* Set version field to 0 */ + memset(&buf[1], 0, 4); + + len = 1 + 4 + 1 + dcid.datalen + 1 + scid.datalen; + + CU_ASSERT((ngtcp2_ssize)len == rv - NGTCP2_PKT_LENGTHLEN - 4 /* pkt_num */); + + rv = pkt_decode_hd_long(&nhd, buf, len); + + CU_ASSERT((ngtcp2_ssize)len == rv); + CU_ASSERT(NGTCP2_PKT_VERSION_NEGOTIATION == nhd.type); + CU_ASSERT((hd.flags & ~NGTCP2_PKT_FLAG_LONG_FORM) == nhd.flags); + CU_ASSERT(ngtcp2_cid_eq(&hd.dcid, &nhd.dcid)); + CU_ASSERT(ngtcp2_cid_eq(&hd.scid, &nhd.scid)); + CU_ASSERT(hd.pkt_num == nhd.pkt_num); + CU_ASSERT(0 == nhd.version); + CU_ASSERT(hd.len == nhd.len); +} + +void test_ngtcp2_pkt_decode_hd_short(void) { + ngtcp2_pkt_hd hd, nhd; + uint8_t buf[256]; + ngtcp2_ssize rv; + size_t expectedlen; + ngtcp2_cid dcid, zcid; + + dcid_init(&dcid); + ngtcp2_cid_zero(&zcid); + + /* 4 bytes packet number */ + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_1RTT, &dcid, NULL, + 0xe1e2e3e4u, 4, 0xd1d2d3d4u, 0); + + expectedlen = 1 + dcid.datalen + 4; + + rv = ngtcp2_pkt_encode_hd_short(buf, sizeof(buf), &hd); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(buf[0] & NGTCP2_FIXED_BIT_MASK); + + rv = pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(hd.flags == nhd.flags); + CU_ASSERT(NGTCP2_PKT_1RTT == nhd.type); + CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid)); + CU_ASSERT(ngtcp2_cid_empty(&nhd.scid)); + CU_ASSERT(0xe1e2e3e4u == nhd.pkt_num); + CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen); + CU_ASSERT(0 == nhd.version); + CU_ASSERT(0 == nhd.len); + + /* 4 bytes packet number without Fixed Bit set */ + ngtcp2_pkt_hd_init( + &hd, NGTCP2_PKT_FLAG_NONE | NGTCP2_PKT_FLAG_FIXED_BIT_CLEAR, + NGTCP2_PKT_1RTT, &dcid, NULL, 0xe1e2e3e4u, 4, 0xd1d2d3d4u, 0); + + expectedlen = 1 + dcid.datalen + 4; + + rv = ngtcp2_pkt_encode_hd_short(buf, sizeof(buf), &hd); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT((buf[0] & NGTCP2_FIXED_BIT_MASK) == 0); + + rv = pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(hd.flags == nhd.flags); + CU_ASSERT(NGTCP2_PKT_1RTT == nhd.type); + CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid)); + CU_ASSERT(ngtcp2_cid_empty(&nhd.scid)); + CU_ASSERT(0xe1e2e3e4u == nhd.pkt_num); + CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen); + CU_ASSERT(0 == nhd.version); + CU_ASSERT(0 == nhd.len); + + /* 2 bytes packet number */ + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_1RTT, &dcid, NULL, + 0xe1e2e3e4u, 2, 0xd1d2d3d4u, 0); + + expectedlen = 1 + dcid.datalen + 2; + + rv = ngtcp2_pkt_encode_hd_short(buf, sizeof(buf), &hd); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + + rv = pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(hd.flags == nhd.flags); + CU_ASSERT(NGTCP2_PKT_1RTT == nhd.type); + CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid)); + CU_ASSERT(ngtcp2_cid_empty(&nhd.scid)); + CU_ASSERT(0xe3e4u == nhd.pkt_num); + CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen); + CU_ASSERT(0 == nhd.version); + CU_ASSERT(0 == nhd.len); + + /* 1 byte packet number */ + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_1RTT, &dcid, NULL, + 0xe1e2e3e4u, 1, 0xd1d2d3d4u, 0); + + expectedlen = 1 + dcid.datalen + 1; + + rv = ngtcp2_pkt_encode_hd_short(buf, sizeof(buf), &hd); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + + rv = pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(hd.flags == nhd.flags); + CU_ASSERT(NGTCP2_PKT_1RTT == nhd.type); + CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid)); + CU_ASSERT(ngtcp2_cid_empty(&nhd.scid)); + CU_ASSERT(0xe4 == nhd.pkt_num); + CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen); + CU_ASSERT(0 == nhd.version); + CU_ASSERT(0 == nhd.len); + + /* With Key Phase */ + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_KEY_PHASE, NGTCP2_PKT_1RTT, &dcid, + NULL, 0xe1e2e3e4u, 4, 0xd1d2d3d4u, 0); + + expectedlen = 1 + dcid.datalen + 4; + + rv = ngtcp2_pkt_encode_hd_short(buf, sizeof(buf), &hd); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + + rv = pkt_decode_hd_short(&nhd, buf, expectedlen, dcid.datalen); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + /* key phase bit is protected by header protection and + ngtcp2_pkt_decode_hd_short does not decode it. */ + CU_ASSERT(NGTCP2_PKT_FLAG_NONE == nhd.flags); + CU_ASSERT(NGTCP2_PKT_1RTT == nhd.type); + CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid)); + CU_ASSERT(ngtcp2_cid_empty(&nhd.scid)); + CU_ASSERT(0xe1e2e3e4u == nhd.pkt_num); + CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen); + CU_ASSERT(0 == nhd.version); + CU_ASSERT(0 == nhd.len); + + /* With empty DCID */ + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_1RTT, NULL, NULL, + 0xe1e2e3e4u, 4, 0xd1d2d3d4u, 0); + + expectedlen = 1 + 4; + + rv = ngtcp2_pkt_encode_hd_short(buf, sizeof(buf), &hd); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + + rv = pkt_decode_hd_short(&nhd, buf, expectedlen, 0); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(hd.flags == nhd.flags); + CU_ASSERT(NGTCP2_PKT_1RTT == nhd.type); + CU_ASSERT(ngtcp2_cid_empty(&nhd.dcid)); + CU_ASSERT(ngtcp2_cid_empty(&nhd.scid)); + CU_ASSERT(0xe1e2e3e4u == nhd.pkt_num); + CU_ASSERT(hd.pkt_numlen == nhd.pkt_numlen); + CU_ASSERT(0 == nhd.version); + CU_ASSERT(0 == nhd.len); +} + +void test_ngtcp2_pkt_decode_stream_frame(void) { + uint8_t buf[256]; + size_t buflen; + ngtcp2_frame fr; + ngtcp2_ssize rv; + size_t expectedlen; + + /* 32 bits Stream ID + 62 bits Offset + Data Length */ + buflen = ngtcp2_t_encode_stream_frame(buf, NGTCP2_STREAM_LEN_BIT, 0xf1f2f3f4u, + 0x31f2f3f4f5f6f7f8llu, 0x14); + + expectedlen = 1 + 8 + 8 + 1 + 20; + + CU_ASSERT(expectedlen == buflen); + + rv = ngtcp2_pkt_decode_stream_frame(&fr.stream, buf, buflen); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(0 == fr.stream.fin); + CU_ASSERT(0xf1f2f3f4u == fr.stream.stream_id); + CU_ASSERT(0x31f2f3f4f5f6f7f8llu == fr.stream.offset); + CU_ASSERT(1 == fr.stream.datacnt); + CU_ASSERT(0x14 == fr.stream.data[0].len); + + /* Cutting 1 bytes from the tail must cause invalid argument + error */ + rv = ngtcp2_pkt_decode_stream_frame(&fr.stream, buf, buflen - 1); + + CU_ASSERT(NGTCP2_ERR_FRAME_ENCODING == rv); + + memset(&fr, 0, sizeof(fr)); + + /* 6 bits Stream ID + no Offset + Data Length */ + buflen = ngtcp2_t_encode_stream_frame(buf, NGTCP2_STREAM_LEN_BIT, 0x31, 0x00, + 0x14); + + expectedlen = 1 + 1 + 0 + 1 + 20; + + CU_ASSERT(expectedlen == buflen); + + rv = ngtcp2_pkt_decode_stream_frame(&fr.stream, buf, buflen); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(0 == fr.stream.fin); + CU_ASSERT(0x31 == fr.stream.stream_id); + CU_ASSERT(0x00 == fr.stream.offset); + CU_ASSERT(1 == fr.stream.datacnt); + CU_ASSERT(0x14 == fr.stream.data[0].len); + + /* Cutting 1 bytes from the tail must cause invalid argument + error */ + rv = ngtcp2_pkt_decode_stream_frame(&fr.stream, buf, buflen - 1); + + CU_ASSERT(NGTCP2_ERR_FRAME_ENCODING == rv); + + memset(&fr, 0, sizeof(fr)); + + /* Fin bit set + no Data Length */ + buflen = ngtcp2_t_encode_stream_frame(buf, NGTCP2_STREAM_FIN_BIT, 0x31f2f3f4u, + 0x00, 0x14); + + expectedlen = 1 + 4 + 20; + + CU_ASSERT(expectedlen == buflen); + + rv = ngtcp2_pkt_decode_stream_frame(&fr.stream, buf, buflen); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(1 == fr.stream.fin); + CU_ASSERT(0x31f2f3f4u == fr.stream.stream_id); + CU_ASSERT(0x00 == fr.stream.offset); + CU_ASSERT(1 == fr.stream.datacnt); + CU_ASSERT(0x14 == fr.stream.data[0].len); + + memset(&fr, 0, sizeof(fr)); +} + +void test_ngtcp2_pkt_decode_ack_frame(void) { + uint8_t buf[256]; + size_t buflen; + ngtcp2_frame fr; + ngtcp2_ssize rv; + size_t expectedlen; + + /* 62 bits Largest Acknowledged */ + buflen = ngtcp2_t_encode_ack_frame(buf, 0x31f2f3f4f5f6f7f8llu, + 0x31e2e3e4e5e6e7e8llu, 99, + 0x31d2d3d4d5d6d7d8llu); + + expectedlen = 1 + 8 + 1 + 1 + 8 + 2 + 8; + + CU_ASSERT(expectedlen == buflen); + + rv = ngtcp2_pkt_decode_ack_frame(&fr.ack, buf, buflen); + + CU_ASSERT((ngtcp2_ssize)expectedlen == rv); + CU_ASSERT(0x31f2f3f4f5f6f7f8llu == fr.ack.largest_ack); + CU_ASSERT(1 == fr.ack.rangecnt); + CU_ASSERT(0x31e2e3e4e5e6e7e8llu == fr.ack.first_ack_range); + CU_ASSERT(99 == fr.ack.ranges[0].gap); + CU_ASSERT(0x31d2d3d4d5d6d7d8llu == fr.ack.ranges[0].len); +} + +void test_ngtcp2_pkt_decode_padding_frame(void) { + uint8_t buf[256]; + ngtcp2_frame fr; + ngtcp2_ssize rv; + size_t paddinglen = 31; + + memset(buf, 0, paddinglen); + buf[paddinglen] = NGTCP2_FRAME_STREAM; + + rv = ngtcp2_pkt_decode_padding_frame(&fr.padding, buf, paddinglen + 1); + + CU_ASSERT((ngtcp2_ssize)paddinglen == rv); + CU_ASSERT((size_t)31 == fr.padding.len); +} + +void test_ngtcp2_pkt_encode_stream_frame(void) { + const uint8_t data[] = "0123456789abcdef0"; + uint8_t buf[256]; + ngtcp2_frame fr, nfr; + ngtcp2_ssize rv; + size_t framelen; + size_t i; + + /* 32 bits Stream ID + 62 bits Offset + Data Length */ + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.fin = 0; + fr.stream.stream_id = 0xf1f2f3f4u; + fr.stream.offset = 0x31f2f3f4f5f6f7f8llu; + fr.stream.datacnt = 1; + fr.stream.data[0].len = strsize(data); + fr.stream.data[0].base = (uint8_t *)data; + + framelen = 1 + 8 + 8 + 1 + 17; + + rv = ngtcp2_pkt_encode_stream_frame(buf, sizeof(buf), &fr.stream); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_stream_frame(&nfr.stream, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT((NGTCP2_STREAM_OFF_BIT | NGTCP2_STREAM_LEN_BIT) == + nfr.stream.flags); + CU_ASSERT(fr.stream.fin == nfr.stream.fin); + CU_ASSERT(fr.stream.stream_id == nfr.stream.stream_id); + CU_ASSERT(fr.stream.offset == nfr.stream.offset); + CU_ASSERT(1 == nfr.stream.datacnt); + CU_ASSERT(fr.stream.data[0].len == nfr.stream.data[0].len); + CU_ASSERT(0 == memcmp(fr.stream.data[0].base, nfr.stream.data[0].base, + fr.stream.data[0].len)); + + memset(&nfr, 0, sizeof(nfr)); + + /* 6 bits Stream ID + No Offset + Data Length */ + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.fin = 0; + fr.stream.stream_id = 0x31; + fr.stream.offset = 0; + fr.stream.datacnt = 1; + fr.stream.data[0].len = strsize(data); + fr.stream.data[0].base = (uint8_t *)data; + + framelen = 1 + 1 + 1 + 17; + + rv = ngtcp2_pkt_encode_stream_frame(buf, sizeof(buf), &fr.stream); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_stream_frame(&nfr.stream, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(NGTCP2_STREAM_LEN_BIT == nfr.stream.flags); + CU_ASSERT(fr.stream.fin == nfr.stream.fin); + CU_ASSERT(fr.stream.stream_id == nfr.stream.stream_id); + CU_ASSERT(fr.stream.offset == nfr.stream.offset); + CU_ASSERT(1 == nfr.stream.datacnt); + CU_ASSERT(fr.stream.data[0].len == nfr.stream.data[0].len); + CU_ASSERT(0 == memcmp(fr.stream.data[0].base, nfr.stream.data[0].base, + fr.stream.data[0].len)); + + memset(&nfr, 0, sizeof(nfr)); + + /* Fin + 32 bits Stream ID + 62 bits Offset + Data Length */ + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.fin = 1; + fr.stream.stream_id = 0xf1f2f3f4u; + fr.stream.offset = 0x31f2f3f4f5f6f7f8llu; + fr.stream.datacnt = 1; + fr.stream.data[0].len = strsize(data); + fr.stream.data[0].base = (uint8_t *)data; + + framelen = 1 + 8 + 8 + 1 + 17; + + rv = ngtcp2_pkt_encode_stream_frame(buf, sizeof(buf), &fr.stream); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_stream_frame(&nfr.stream, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT((NGTCP2_STREAM_FIN_BIT | NGTCP2_STREAM_OFF_BIT | + NGTCP2_STREAM_LEN_BIT) == nfr.stream.flags); + CU_ASSERT(fr.stream.fin == nfr.stream.fin); + CU_ASSERT(fr.stream.stream_id == nfr.stream.stream_id); + CU_ASSERT(fr.stream.offset == nfr.stream.offset); + CU_ASSERT(1 == nfr.stream.datacnt); + CU_ASSERT(fr.stream.data[0].len == nfr.stream.data[0].len); + CU_ASSERT(0 == memcmp(fr.stream.data[0].base, nfr.stream.data[0].base, + fr.stream.data[0].len)); + + /* Make sure that we check the length properly. */ + for (i = 1; i < framelen; ++i) { + rv = ngtcp2_pkt_decode_stream_frame(&nfr.stream, buf, i); + + CU_ASSERT(NGTCP2_ERR_FRAME_ENCODING == rv); + } + + memset(&nfr, 0, sizeof(nfr)); + + /* NOBUF: Fin + 32 bits Stream ID + 62 bits Offset + Data Length */ + fr.type = NGTCP2_FRAME_STREAM; + fr.stream.fin = 1; + fr.stream.stream_id = 0xf1f2f3f4u; + fr.stream.offset = 0x31f2f3f4f5f6f7f8llu; + fr.stream.datacnt = 1; + fr.stream.data[0].len = strsize(data); + fr.stream.data[0].base = (uint8_t *)data; + + framelen = 1 + 8 + 8 + 1 + 17; + + rv = ngtcp2_pkt_encode_stream_frame(buf, framelen - 1, &fr.stream); + + CU_ASSERT(NGTCP2_ERR_NOBUF == rv); +} + +void test_ngtcp2_pkt_encode_ack_frame(void) { + uint8_t buf[256]; + ngtcp2_max_frame mfr, nmfr; + ngtcp2_frame *fr = &mfr.fr, *nfr = &nmfr.fr; + ngtcp2_ssize rv; + size_t framelen; + size_t i; + ngtcp2_ack_range *ranges; + + /* 0 Num Blocks */ + fr->type = NGTCP2_FRAME_ACK; + fr->ack.largest_ack = 0xf1f2f3f4llu; + fr->ack.first_ack_range = 0; + fr->ack.ack_delay = 0; + fr->ack.rangecnt = 0; + + framelen = 1 + 8 + 1 + 1 + 1; + + rv = ngtcp2_pkt_encode_ack_frame(buf, sizeof(buf), &fr->ack); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_ack_frame(&nfr->ack, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr->type == nfr->type); + CU_ASSERT(fr->ack.largest_ack == nfr->ack.largest_ack); + CU_ASSERT(fr->ack.ack_delay == nfr->ack.ack_delay); + CU_ASSERT(fr->ack.rangecnt == nfr->ack.rangecnt); + + memset(&nmfr, 0, sizeof(nmfr)); + + /* 2 Num Blocks */ + fr->type = NGTCP2_FRAME_ACK; + fr->ack.largest_ack = 0xf1f2f3f4llu; + fr->ack.first_ack_range = 0xe1e2e3e4llu; + fr->ack.ack_delay = 0xf1f2; + fr->ack.rangecnt = 2; + ranges = fr->ack.ranges; + ranges[0].gap = 255; + ranges[0].len = 0xd1d2d3d4llu; + ranges[1].gap = 1; + ranges[1].len = 0xd1d2d3d4llu; + + framelen = 1 + 8 + 4 + 1 + 8 + (2 + 8) + (1 + 8); + + rv = ngtcp2_pkt_encode_ack_frame(buf, sizeof(buf), &fr->ack); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_ack_frame(&nfr->ack, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr->type == nfr->type); + CU_ASSERT(fr->ack.largest_ack == nfr->ack.largest_ack); + CU_ASSERT(fr->ack.ack_delay == nfr->ack.ack_delay); + CU_ASSERT(fr->ack.rangecnt == nfr->ack.rangecnt); + + for (i = 0; i < fr->ack.rangecnt; ++i) { + CU_ASSERT(fr->ack.ranges[i].gap == nfr->ack.ranges[i].gap); + CU_ASSERT(fr->ack.ranges[i].len == nfr->ack.ranges[i].len); + } + + memset(&nmfr, 0, sizeof(nmfr)); +} + +void test_ngtcp2_pkt_encode_ack_ecn_frame(void) { + uint8_t buf[256]; + ngtcp2_max_frame mfr, nmfr; + ngtcp2_frame *fr = &mfr.fr, *nfr = &nmfr.fr; + ngtcp2_ssize rv; + size_t framelen; + size_t i; + ngtcp2_ack_range *ranges; + + /* 0 Num Blocks */ + fr->type = NGTCP2_FRAME_ACK_ECN; + fr->ack.largest_ack = 0xf1f2f3f4llu; + fr->ack.first_ack_range = 0; + fr->ack.ack_delay = 0; + fr->ack.rangecnt = 0; + fr->ack.ecn.ect0 = 64; + fr->ack.ecn.ect1 = 16384; + fr->ack.ecn.ce = 1073741824; + + framelen = 1 + 8 + 1 + 1 + 1 + 2 + 4 + 8; + + rv = ngtcp2_pkt_encode_ack_frame(buf, sizeof(buf), &fr->ack); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_ack_frame(&nfr->ack, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr->type == nfr->type); + CU_ASSERT(fr->ack.largest_ack == nfr->ack.largest_ack); + CU_ASSERT(fr->ack.ack_delay == nfr->ack.ack_delay); + CU_ASSERT(fr->ack.rangecnt == nfr->ack.rangecnt); + CU_ASSERT(fr->ack.ecn.ect0 == nfr->ack.ecn.ect0); + CU_ASSERT(fr->ack.ecn.ect1 == nfr->ack.ecn.ect1); + CU_ASSERT(fr->ack.ecn.ce == nfr->ack.ecn.ce); + + memset(&nmfr, 0, sizeof(nmfr)); + + /* 2 Num Blocks */ + fr->type = NGTCP2_FRAME_ACK_ECN; + fr->ack.largest_ack = 0xf1f2f3f4llu; + fr->ack.first_ack_range = 0xe1e2e3e4llu; + fr->ack.ack_delay = 0xf1f2; + fr->ack.rangecnt = 2; + ranges = fr->ack.ranges; + ranges[0].gap = 255; + ranges[0].len = 0xd1d2d3d4llu; + ranges[1].gap = 1; + ranges[1].len = 0xd1d2d3d4llu; + fr->ack.ecn.ect0 = 0; + fr->ack.ecn.ect1 = 64; + fr->ack.ecn.ce = 16384; + + framelen = 1 + 8 + 4 + 1 + 8 + (2 + 8) + (1 + 8) + 1 + 2 + 4; + + rv = ngtcp2_pkt_encode_ack_frame(buf, sizeof(buf), &fr->ack); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_ack_frame(&nfr->ack, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr->type == nfr->type); + CU_ASSERT(fr->ack.largest_ack == nfr->ack.largest_ack); + CU_ASSERT(fr->ack.ack_delay == nfr->ack.ack_delay); + CU_ASSERT(fr->ack.rangecnt == nfr->ack.rangecnt); + + for (i = 0; i < fr->ack.rangecnt; ++i) { + CU_ASSERT(fr->ack.ranges[i].gap == nfr->ack.ranges[i].gap); + CU_ASSERT(fr->ack.ranges[i].len == nfr->ack.ranges[i].len); + } + + CU_ASSERT(fr->ack.ecn.ect0 == nfr->ack.ecn.ect0); + CU_ASSERT(fr->ack.ecn.ect1 == nfr->ack.ecn.ect1); + CU_ASSERT(fr->ack.ecn.ce == nfr->ack.ecn.ce); + + memset(&nmfr, 0, sizeof(nmfr)); +} + +void test_ngtcp2_pkt_encode_reset_stream_frame(void) { + uint8_t buf[32]; + ngtcp2_reset_stream fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 4 + 4 + 8; + + fr.type = NGTCP2_FRAME_RESET_STREAM; + fr.stream_id = 1000000007; + fr.app_error_code = 0xe1e2; + fr.final_size = 0x31f2f3f4f5f6f7f8llu; + + rv = ngtcp2_pkt_encode_reset_stream_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_reset_stream_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.stream_id == nfr.stream_id); + CU_ASSERT(fr.app_error_code == nfr.app_error_code); + CU_ASSERT(fr.final_size == nfr.final_size); +} + +void test_ngtcp2_pkt_encode_connection_close_frame(void) { + uint8_t buf[2048]; + ngtcp2_frame fr, nfr; + ngtcp2_ssize rv; + size_t framelen; + uint8_t reason[1024]; + + memset(reason, 0xfa, sizeof(reason)); + + /* no Reason Phrase */ + fr.type = NGTCP2_FRAME_CONNECTION_CLOSE; + fr.connection_close.error_code = 0xf1f2u; + fr.connection_close.frame_type = 255; + fr.connection_close.reasonlen = 0; + fr.connection_close.reason = NULL; + + framelen = 1 + 4 + 2 + 1; + + rv = ngtcp2_pkt_encode_connection_close_frame(buf, sizeof(buf), + &fr.connection_close); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_connection_close_frame(&nfr.connection_close, buf, + framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.connection_close.error_code == nfr.connection_close.error_code); + CU_ASSERT(fr.connection_close.reasonlen == nfr.connection_close.reasonlen); + CU_ASSERT(fr.connection_close.reason == nfr.connection_close.reason); + + memset(&nfr, 0, sizeof(nfr)); + + /* 1024 bytes Reason Phrase */ + fr.type = NGTCP2_FRAME_CONNECTION_CLOSE; + fr.connection_close.error_code = 0xf3f4u; + fr.connection_close.frame_type = 0; + fr.connection_close.reasonlen = sizeof(reason); + fr.connection_close.reason = reason; + + framelen = 1 + 4 + 1 + 2 + sizeof(reason); + + rv = ngtcp2_pkt_encode_connection_close_frame(buf, sizeof(buf), + &fr.connection_close); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_connection_close_frame(&nfr.connection_close, buf, + framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.connection_close.error_code == nfr.connection_close.error_code); + CU_ASSERT(fr.connection_close.reasonlen == nfr.connection_close.reasonlen); + CU_ASSERT(0 == memcmp(reason, nfr.connection_close.reason, sizeof(reason))); + + memset(&nfr, 0, sizeof(nfr)); +} + +void test_ngtcp2_pkt_encode_connection_close_app_frame(void) { + uint8_t buf[2048]; + ngtcp2_frame fr, nfr; + ngtcp2_ssize rv; + size_t framelen; + uint8_t reason[1024]; + + memset(reason, 0xfa, sizeof(reason)); + + /* no Reason Phrase */ + fr.type = NGTCP2_FRAME_CONNECTION_CLOSE_APP; + fr.connection_close.error_code = 0xf1f2u; + fr.connection_close.frame_type = 0xff; /* This must be ignored. */ + fr.connection_close.reasonlen = 0; + fr.connection_close.reason = NULL; + + framelen = 1 + 4 + 1; + + rv = ngtcp2_pkt_encode_connection_close_frame(buf, sizeof(buf), + &fr.connection_close); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_connection_close_frame(&nfr.connection_close, buf, + framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.connection_close.error_code == nfr.connection_close.error_code); + CU_ASSERT(0 == nfr.connection_close.frame_type); + CU_ASSERT(fr.connection_close.reasonlen == nfr.connection_close.reasonlen); + CU_ASSERT(fr.connection_close.reason == nfr.connection_close.reason); + + memset(&nfr, 0, sizeof(nfr)); +} + +void test_ngtcp2_pkt_encode_max_data_frame(void) { + uint8_t buf[16]; + ngtcp2_max_data fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 8; + + fr.type = NGTCP2_FRAME_MAX_DATA; + fr.max_data = 0x31f2f3f4f5f6f7f8llu; + + rv = ngtcp2_pkt_encode_max_data_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_max_data_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.max_data == nfr.max_data); +} + +void test_ngtcp2_pkt_encode_max_stream_data_frame(void) { + uint8_t buf[17]; + ngtcp2_max_stream_data fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 8 + 8; + + fr.type = NGTCP2_FRAME_MAX_STREAM_DATA; + fr.stream_id = 0xf1f2f3f4u; + fr.max_stream_data = 0x35f6f7f8f9fafbfcllu; + + rv = ngtcp2_pkt_encode_max_stream_data_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_max_stream_data_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.stream_id == nfr.stream_id); + CU_ASSERT(fr.max_stream_data == nfr.max_stream_data); +} + +void test_ngtcp2_pkt_encode_max_streams_frame(void) { + uint8_t buf[16]; + ngtcp2_max_streams fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 8; + + fr.type = NGTCP2_FRAME_MAX_STREAMS_BIDI; + fr.max_streams = 0xf1f2f3f4u; + + rv = ngtcp2_pkt_encode_max_streams_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_max_streams_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.max_streams == nfr.max_streams); +} + +void test_ngtcp2_pkt_encode_ping_frame(void) { + uint8_t buf[3]; + ngtcp2_ping fr, nfr; + ngtcp2_ssize rv; + size_t framelen; + + fr.type = NGTCP2_FRAME_PING; + + framelen = 1; + + rv = ngtcp2_pkt_encode_ping_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_ping_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); +} + +void test_ngtcp2_pkt_encode_data_blocked_frame(void) { + uint8_t buf[9]; + ngtcp2_data_blocked fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 8; + + fr.type = NGTCP2_FRAME_DATA_BLOCKED; + fr.offset = 0x31f2f3f4f5f6f7f8llu; + + rv = ngtcp2_pkt_encode_data_blocked_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_data_blocked_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.offset == nfr.offset); +} + +void test_ngtcp2_pkt_encode_stream_data_blocked_frame(void) { + uint8_t buf[17]; + ngtcp2_stream_data_blocked fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 8 + 8; + + fr.type = NGTCP2_FRAME_STREAM_DATA_BLOCKED; + fr.stream_id = 0xf1f2f3f4u; + fr.offset = 0x35f6f7f8f9fafbfcllu; + + rv = ngtcp2_pkt_encode_stream_data_blocked_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_stream_data_blocked_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.stream_id == nfr.stream_id); + CU_ASSERT(fr.offset == nfr.offset); +} + +void test_ngtcp2_pkt_encode_streams_blocked_frame(void) { + uint8_t buf[9]; + ngtcp2_streams_blocked fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 8; + + fr.type = NGTCP2_FRAME_STREAMS_BLOCKED_BIDI; + fr.max_streams = 0xf1f2f3f4u; + + rv = ngtcp2_pkt_encode_streams_blocked_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_streams_blocked_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.max_streams == nfr.max_streams); +} + +void test_ngtcp2_pkt_encode_new_connection_id_frame(void) { + uint8_t buf[256]; + ngtcp2_new_connection_id fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 4 + 2 + 1 + 18 + NGTCP2_STATELESS_RESET_TOKENLEN; + + fr.type = NGTCP2_FRAME_NEW_CONNECTION_ID; + fr.seq = 1000000009; + fr.retire_prior_to = 255; + scid_init(&fr.cid); + memset(fr.stateless_reset_token, 0xe1, sizeof(fr.stateless_reset_token)); + + rv = ngtcp2_pkt_encode_new_connection_id_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_new_connection_id_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.seq == nfr.seq); + CU_ASSERT(ngtcp2_cid_eq(&fr.cid, &nfr.cid)); + CU_ASSERT(0 == memcmp(fr.stateless_reset_token, nfr.stateless_reset_token, + sizeof(fr.stateless_reset_token))); +} + +void test_ngtcp2_pkt_encode_stop_sending_frame(void) { + uint8_t buf[16]; + ngtcp2_stop_sending fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 8 + 4; + + fr.type = NGTCP2_FRAME_STOP_SENDING; + fr.stream_id = 0xf1f2f3f4u; + fr.app_error_code = 0xe1e2u; + + rv = ngtcp2_pkt_encode_stop_sending_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_stop_sending_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.stream_id == nfr.stream_id); + CU_ASSERT(fr.app_error_code == nfr.app_error_code); +} + +void test_ngtcp2_pkt_encode_path_challenge_frame(void) { + uint8_t buf[9]; + ngtcp2_path_challenge fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 8; + size_t i; + + fr.type = NGTCP2_FRAME_PATH_CHALLENGE; + for (i = 0; i < sizeof(fr.data); ++i) { + fr.data[i] = (uint8_t)(i + 1); + } + + rv = ngtcp2_pkt_encode_path_challenge_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_path_challenge_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(0 == memcmp(fr.data, nfr.data, sizeof(fr.data))); +} + +void test_ngtcp2_pkt_encode_path_response_frame(void) { + uint8_t buf[9]; + ngtcp2_path_response fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1 + 8; + size_t i; + + fr.type = NGTCP2_FRAME_PATH_RESPONSE; + for (i = 0; i < sizeof(fr.data); ++i) { + fr.data[i] = (uint8_t)(i + 1); + } + + rv = ngtcp2_pkt_encode_path_response_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_path_response_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(0 == memcmp(fr.data, nfr.data, sizeof(fr.data))); +} + +void test_ngtcp2_pkt_encode_crypto_frame(void) { + const uint8_t data[] = "0123456789abcdef1"; + uint8_t buf[256]; + ngtcp2_frame fr, nfr; + ngtcp2_ssize rv; + size_t framelen; + + fr.type = NGTCP2_FRAME_CRYPTO; + fr.crypto.offset = 0x31f2f3f4f5f6f7f8llu; + fr.crypto.datacnt = 1; + fr.crypto.data[0].len = strsize(data); + fr.crypto.data[0].base = (uint8_t *)data; + + framelen = 1 + 8 + 1 + 17; + + rv = ngtcp2_pkt_encode_crypto_frame(buf, sizeof(buf), &fr.crypto); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_crypto_frame(&nfr.crypto, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.crypto.offset == nfr.crypto.offset); + CU_ASSERT(fr.crypto.datacnt == nfr.crypto.datacnt); + CU_ASSERT(fr.crypto.data[0].len == nfr.crypto.data[0].len); + CU_ASSERT(0 == memcmp(fr.crypto.data[0].base, nfr.crypto.data[0].base, + fr.crypto.data[0].len)); +} + +void test_ngtcp2_pkt_encode_new_token_frame(void) { + const uint8_t token[] = "0123456789abcdef2"; + uint8_t buf[256]; + ngtcp2_frame fr, nfr; + ngtcp2_ssize rv; + size_t framelen; + + fr.type = NGTCP2_FRAME_NEW_TOKEN; + ngtcp2_vec_init(&fr.new_token.token, token, strsize(token)); + + framelen = 1 + 1 + strsize(token); + + rv = ngtcp2_pkt_encode_new_token_frame(buf, sizeof(buf), &fr.new_token); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_new_token_frame(&nfr.new_token, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.new_token.token.len == nfr.new_token.token.len); + CU_ASSERT(0 == memcmp(fr.new_token.token.base, nfr.new_token.token.base, + fr.new_token.token.len)); +} + +void test_ngtcp2_pkt_encode_retire_connection_id_frame(void) { + uint8_t buf[256]; + ngtcp2_frame fr, nfr; + ngtcp2_ssize rv; + size_t framelen; + + fr.type = NGTCP2_FRAME_RETIRE_CONNECTION_ID; + fr.retire_connection_id.seq = 1000000007; + + framelen = 1 + ngtcp2_put_uvarintlen(fr.retire_connection_id.seq); + + rv = ngtcp2_pkt_encode_retire_connection_id_frame(buf, sizeof(buf), + &fr.retire_connection_id); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_retire_connection_id_frame(&nfr.retire_connection_id, + buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.retire_connection_id.seq == nfr.retire_connection_id.seq); +} + +void test_ngtcp2_pkt_encode_handshake_done_frame(void) { + uint8_t buf[16]; + ngtcp2_handshake_done fr, nfr; + ngtcp2_ssize rv; + size_t framelen = 1; + + fr.type = NGTCP2_FRAME_HANDSHAKE_DONE; + + rv = ngtcp2_pkt_encode_handshake_done_frame(buf, sizeof(buf), &fr); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_handshake_done_frame(&nfr, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); +} + +void test_ngtcp2_pkt_encode_datagram_frame(void) { + const uint8_t data[] = "0123456789abcdef3"; + uint8_t buf[256]; + ngtcp2_frame fr, nfr; + ngtcp2_ssize rv; + size_t framelen; + + fr.type = NGTCP2_FRAME_DATAGRAM_LEN; + fr.datagram.datacnt = 1; + fr.datagram.data = fr.datagram.rdata; + fr.datagram.rdata[0].len = strsize(data); + fr.datagram.rdata[0].base = (uint8_t *)data; + + framelen = 1 + 1 + 17; + + rv = ngtcp2_pkt_encode_datagram_frame(buf, sizeof(buf), &fr.datagram); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_datagram_frame(&nfr.datagram, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.datagram.datacnt == nfr.datagram.datacnt); + CU_ASSERT(fr.datagram.data->len == nfr.datagram.data->len); + CU_ASSERT(0 == memcmp(fr.datagram.data->base, nfr.datagram.data->base, + fr.datagram.data->len)); + + memset(&nfr, 0, sizeof(nfr)); + + /* Without length field */ + fr.type = NGTCP2_FRAME_DATAGRAM; + fr.datagram.datacnt = 1; + fr.datagram.data = fr.datagram.rdata; + fr.datagram.rdata[0].len = strsize(data); + fr.datagram.rdata[0].base = (uint8_t *)data; + + framelen = 1 + 17; + + rv = ngtcp2_pkt_encode_datagram_frame(buf, sizeof(buf), &fr.datagram); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_datagram_frame(&nfr.datagram, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.datagram.datacnt == nfr.datagram.datacnt); + CU_ASSERT(fr.datagram.data->len == nfr.datagram.data->len); + CU_ASSERT(0 == memcmp(fr.datagram.data->base, nfr.datagram.data->base, + fr.datagram.data->len)); + + memset(&nfr, 0, sizeof(nfr)); + + /* Zero length data with length field */ + fr.type = NGTCP2_FRAME_DATAGRAM_LEN; + fr.datagram.datacnt = 0; + fr.datagram.data = NULL; + + framelen = 1 + 1; + + rv = ngtcp2_pkt_encode_datagram_frame(buf, sizeof(buf), &fr.datagram); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_datagram_frame(&nfr.datagram, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.datagram.datacnt == nfr.datagram.datacnt); + CU_ASSERT(NULL == nfr.datagram.data); + + /* Zero length data without length field */ + fr.type = NGTCP2_FRAME_DATAGRAM; + fr.datagram.datacnt = 0; + fr.datagram.data = NULL; + + framelen = 1; + + rv = ngtcp2_pkt_encode_datagram_frame(buf, sizeof(buf), &fr.datagram); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + + rv = ngtcp2_pkt_decode_datagram_frame(&nfr.datagram, buf, framelen); + + CU_ASSERT((ngtcp2_ssize)framelen == rv); + CU_ASSERT(fr.type == nfr.type); + CU_ASSERT(fr.datagram.datacnt == nfr.datagram.datacnt); + CU_ASSERT(NULL == nfr.datagram.data); +} + +void test_ngtcp2_pkt_adjust_pkt_num(void) { + CU_ASSERT(0xaa831f94llu == + ngtcp2_pkt_adjust_pkt_num(0xaa82f30ellu, 0x1f94, 16)); + + CU_ASSERT(0xff == ngtcp2_pkt_adjust_pkt_num(0x0100, 0xff, 8)); + CU_ASSERT(0x01ff == ngtcp2_pkt_adjust_pkt_num(0x01ff, 0xff, 8)); + CU_ASSERT(0x0fff == ngtcp2_pkt_adjust_pkt_num(0x1000, 0xff, 8)); + CU_ASSERT(0x80 == ngtcp2_pkt_adjust_pkt_num(0x00, 0x80, 8)); + CU_ASSERT(0x3fffffffffffffabllu == + ngtcp2_pkt_adjust_pkt_num(NGTCP2_MAX_PKT_NUM, 0xab, 8)); + CU_ASSERT(0x4000000000000000llu == + ngtcp2_pkt_adjust_pkt_num(NGTCP2_MAX_PKT_NUM, 0x00, 8)); + CU_ASSERT(250 == ngtcp2_pkt_adjust_pkt_num(255, 250, 8)); + CU_ASSERT(8 == ngtcp2_pkt_adjust_pkt_num(50, 8, 8)); + CU_ASSERT(0 == ngtcp2_pkt_adjust_pkt_num(-1, 0, 8)); +} + +void test_ngtcp2_pkt_validate_ack(void) { + int rv; + ngtcp2_ack fr; + + /* too long first_ack_range */ + fr.largest_ack = 1; + fr.first_ack_range = 2; + fr.rangecnt = 0; + + rv = ngtcp2_pkt_validate_ack(&fr); + + CU_ASSERT(NGTCP2_ERR_ACK_FRAME == rv); + + /* gap is too large */ + fr.largest_ack = 250; + fr.first_ack_range = 1; + fr.rangecnt = 1; + fr.ranges[0].gap = 248; + fr.ranges[0].len = 0; + + rv = ngtcp2_pkt_validate_ack(&fr); + + CU_ASSERT(NGTCP2_ERR_ACK_FRAME == rv); + + /* too large range len */ + fr.largest_ack = 250; + fr.first_ack_range = 0; + fr.rangecnt = 1; + fr.ranges[0].gap = 248; + fr.ranges[0].len = 1; + + rv = ngtcp2_pkt_validate_ack(&fr); + + CU_ASSERT(NGTCP2_ERR_ACK_FRAME == rv); +} + +void test_ngtcp2_pkt_write_stateless_reset(void) { + uint8_t buf[256]; + ngtcp2_ssize spktlen; + uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN]; + uint8_t rand[256]; + size_t i; + uint8_t *p; + size_t randlen; + + memset(rand, 0, sizeof(rand)); + for (i = 0; i < NGTCP2_STATELESS_RESET_TOKENLEN; ++i) { + token[i] = (uint8_t)(i + 1); + } + + spktlen = ngtcp2_pkt_write_stateless_reset(buf, sizeof(buf), token, rand, + sizeof(rand)); + + p = buf; + + CU_ASSERT(256 == spktlen); + CU_ASSERT(0 == (*p & NGTCP2_HEADER_FORM_BIT)); + CU_ASSERT((*p & NGTCP2_FIXED_BIT_MASK)); + + ++p; + + randlen = (size_t)(spktlen - (p - buf) - NGTCP2_STATELESS_RESET_TOKENLEN); + + CU_ASSERT(0 == memcmp(rand, p, randlen)); + + p += randlen; + + CU_ASSERT(0 == memcmp(token, p, NGTCP2_STATELESS_RESET_TOKENLEN)); + + p += NGTCP2_STATELESS_RESET_TOKENLEN; + + CU_ASSERT(spktlen == p - buf); + + /* Not enough buffer */ + spktlen = ngtcp2_pkt_write_stateless_reset( + buf, + NGTCP2_MIN_STATELESS_RESET_RANDLEN - 1 + NGTCP2_STATELESS_RESET_TOKENLEN, + token, rand, sizeof(rand)); + + CU_ASSERT(NGTCP2_ERR_NOBUF == spktlen); +} + +void test_ngtcp2_pkt_write_retry(void) { + uint8_t buf[256]; + ngtcp2_ssize spktlen; + ngtcp2_cid scid, dcid, odcid; + ngtcp2_pkt_hd nhd; + uint8_t token[32]; + size_t i; + ngtcp2_pkt_retry retry; + ngtcp2_ssize nread; + int rv; + ngtcp2_crypto_aead aead = {0}; + ngtcp2_crypto_aead_ctx aead_ctx = {0}; + uint8_t tag[NGTCP2_RETRY_TAGLEN] = {0}; + + scid_init(&scid); + dcid_init(&dcid); + rcid_init(&odcid); + + for (i = 0; i < sizeof(token); ++i) { + token[i] = (uint8_t)i; + } + + spktlen = ngtcp2_pkt_write_retry(buf, sizeof(buf), NGTCP2_PROTO_VER_V1, &dcid, + &scid, &odcid, token, sizeof(token), + null_retry_encrypt, &aead, &aead_ctx); + + CU_ASSERT(spktlen > 0); + + memset(&nhd, 0, sizeof(nhd)); + + nread = ngtcp2_pkt_decode_hd_long(&nhd, buf, (size_t)spktlen); + + CU_ASSERT(nread > 0); + CU_ASSERT(NGTCP2_PKT_RETRY == nhd.type); + CU_ASSERT(NGTCP2_PROTO_VER_V1 == nhd.version); + CU_ASSERT(ngtcp2_cid_eq(&dcid, &nhd.dcid)); + CU_ASSERT(ngtcp2_cid_eq(&scid, &nhd.scid)); + + rv = ngtcp2_pkt_decode_retry(&retry, buf + nread, (size_t)(spktlen - nread)); + + CU_ASSERT(0 == rv); + CU_ASSERT(sizeof(token) == retry.token.len); + CU_ASSERT(0 == memcmp(token, retry.token.base, sizeof(token))); + CU_ASSERT(0 == memcmp(tag, retry.tag, sizeof(tag))); +} + +void test_ngtcp2_pkt_write_version_negotiation(void) { + uint8_t buf[256]; + ngtcp2_ssize spktlen; + const uint32_t sv[] = {0xf1f2f3f4, 0x1f2f3f4f}; + const uint8_t *p; + size_t i; + ngtcp2_cid dcid, scid; + uint32_t v; + + dcid_init(&dcid); + scid_init(&scid); + + spktlen = ngtcp2_pkt_write_version_negotiation( + buf, sizeof(buf), 133, dcid.data, dcid.datalen, scid.data, scid.datalen, + sv, ngtcp2_arraylen(sv)); + + CU_ASSERT((ngtcp2_ssize)(1 + 4 + 1 + dcid.datalen + 1 + scid.datalen + + ngtcp2_arraylen(sv) * 4) == spktlen); + + p = buf; + + CU_ASSERT((0x80 | 133) == buf[0]); + + ++p; + + p = ngtcp2_get_uint32(&v, p); + + CU_ASSERT(0 == v); + + CU_ASSERT(dcid.datalen == *p); + + ++p; + + CU_ASSERT(0 == memcmp(dcid.data, p, dcid.datalen)); + + p += dcid.datalen; + + CU_ASSERT(scid.datalen == *p); + + ++p; + + CU_ASSERT(0 == memcmp(scid.data, p, scid.datalen)); + + p += scid.datalen; + + for (i = 0; i < ngtcp2_arraylen(sv); ++i) { + p = ngtcp2_get_uint32(&v, p); + + CU_ASSERT(sv[i] == v); + } +} + +void test_ngtcp2_pkt_stream_max_datalen(void) { + size_t len; + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 0, 2); + + CU_ASSERT((size_t)-1 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 0, 3); + + CU_ASSERT(0 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 1, 3); + + CU_ASSERT(0 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 1, 4); + + CU_ASSERT(1 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 1, 1, 4); + + CU_ASSERT(0 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 63, 66); + + CU_ASSERT(63 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 63, 65); + + CU_ASSERT(62 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 1396, 1400); + + CU_ASSERT(1396 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 1396, 1399); + + CU_ASSERT(1395 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 1396, 9); + + CU_ASSERT(6 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 16385, 16391); + + CU_ASSERT(16385 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 16385, 16390); + + CU_ASSERT(16384 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 1073741824, 1073741834); + + CU_ASSERT(1073741824 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 1073741824, 1073741833); + + CU_ASSERT(1073741823 == len); + + len = ngtcp2_pkt_stream_max_datalen(63, 0, 16383, 16387); + + CU_ASSERT(16383 == len); +} diff --git a/tests/ngtcp2_pkt_test.h b/tests/ngtcp2_pkt_test.h new file mode 100644 index 0000000..52505eb --- /dev/null +++ b/tests/ngtcp2_pkt_test.h @@ -0,0 +1,68 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_PKT_TEST_H +#define NGTCP2_PKT_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_pkt_decode_version_cid(void); +void test_ngtcp2_pkt_decode_hd_long(void); +void test_ngtcp2_pkt_decode_hd_short(void); +void test_ngtcp2_pkt_decode_stream_frame(void); +void test_ngtcp2_pkt_decode_ack_frame(void); +void test_ngtcp2_pkt_decode_padding_frame(void); +void test_ngtcp2_pkt_encode_stream_frame(void); +void test_ngtcp2_pkt_encode_ack_frame(void); +void test_ngtcp2_pkt_encode_ack_ecn_frame(void); +void test_ngtcp2_pkt_encode_reset_stream_frame(void); +void test_ngtcp2_pkt_encode_connection_close_frame(void); +void test_ngtcp2_pkt_encode_connection_close_app_frame(void); +void test_ngtcp2_pkt_encode_application_close_frame(void); +void test_ngtcp2_pkt_encode_max_data_frame(void); +void test_ngtcp2_pkt_encode_max_stream_data_frame(void); +void test_ngtcp2_pkt_encode_max_streams_frame(void); +void test_ngtcp2_pkt_encode_ping_frame(void); +void test_ngtcp2_pkt_encode_data_blocked_frame(void); +void test_ngtcp2_pkt_encode_stream_data_blocked_frame(void); +void test_ngtcp2_pkt_encode_streams_blocked_frame(void); +void test_ngtcp2_pkt_encode_new_connection_id_frame(void); +void test_ngtcp2_pkt_encode_stop_sending_frame(void); +void test_ngtcp2_pkt_encode_path_challenge_frame(void); +void test_ngtcp2_pkt_encode_path_response_frame(void); +void test_ngtcp2_pkt_encode_crypto_frame(void); +void test_ngtcp2_pkt_encode_new_token_frame(void); +void test_ngtcp2_pkt_encode_retire_connection_id_frame(void); +void test_ngtcp2_pkt_encode_handshake_done_frame(void); +void test_ngtcp2_pkt_encode_datagram_frame(void); +void test_ngtcp2_pkt_adjust_pkt_num(void); +void test_ngtcp2_pkt_validate_ack(void); +void test_ngtcp2_pkt_write_stateless_reset(void); +void test_ngtcp2_pkt_write_retry(void); +void test_ngtcp2_pkt_write_version_negotiation(void); +void test_ngtcp2_pkt_stream_max_datalen(void); + +#endif /* NGTCP2_PKT_TEST_H */ diff --git a/tests/ngtcp2_pmtud_test.c b/tests/ngtcp2_pmtud_test.c new file mode 100644 index 0000000..16f4981 --- /dev/null +++ b/tests/ngtcp2_pmtud_test.c @@ -0,0 +1,153 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_pmtud_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_pmtud.h" +#include "ngtcp2_test_helper.h" + +void test_ngtcp2_pmtud_probe(void) { + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_pmtud *pmtud; + int rv; + + /* Send probe and get success */ + rv = ngtcp2_pmtud_new(&pmtud, NGTCP2_MAX_UDP_PAYLOAD_SIZE, 1452, 0, mem); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == pmtud->mtu_idx); + CU_ASSERT(!ngtcp2_pmtud_finished(pmtud)); + CU_ASSERT(ngtcp2_pmtud_require_probe(pmtud)); + CU_ASSERT(1454 - 48 == ngtcp2_pmtud_probelen(pmtud)); + + ngtcp2_pmtud_probe_sent(pmtud, 2, 0); + + CU_ASSERT(1 == pmtud->num_pkts_sent); + CU_ASSERT(2 == pmtud->expiry); + CU_ASSERT(!ngtcp2_pmtud_require_probe(pmtud)); + + ngtcp2_pmtud_handle_expiry(pmtud, 1); + + CU_ASSERT(!ngtcp2_pmtud_require_probe(pmtud)); + + ngtcp2_pmtud_handle_expiry(pmtud, 2); + + CU_ASSERT(ngtcp2_pmtud_require_probe(pmtud)); + + ngtcp2_pmtud_probe_sent(pmtud, 2, 2); + + CU_ASSERT(2 == pmtud->num_pkts_sent); + CU_ASSERT(4 == pmtud->expiry); + CU_ASSERT(!ngtcp2_pmtud_require_probe(pmtud)); + + ngtcp2_pmtud_handle_expiry(pmtud, 4); + + CU_ASSERT(ngtcp2_pmtud_require_probe(pmtud)); + + ngtcp2_pmtud_probe_sent(pmtud, 2, 4); + + CU_ASSERT(3 == pmtud->num_pkts_sent); + CU_ASSERT(10 == pmtud->expiry); + CU_ASSERT(!ngtcp2_pmtud_require_probe(pmtud)); + + ngtcp2_pmtud_probe_success(pmtud, ngtcp2_pmtud_probelen(pmtud)); + + CU_ASSERT(3 == pmtud->mtu_idx); + CU_ASSERT(UINT64_MAX == pmtud->expiry); + CU_ASSERT(0 == pmtud->num_pkts_sent); + CU_ASSERT(ngtcp2_pmtud_require_probe(pmtud)); + CU_ASSERT(1492 - 48 == ngtcp2_pmtud_probelen(pmtud)); + + ngtcp2_pmtud_probe_sent(pmtud, 2, 10); + ngtcp2_pmtud_handle_expiry(pmtud, 12); + ngtcp2_pmtud_probe_sent(pmtud, 2, 12); + ngtcp2_pmtud_handle_expiry(pmtud, 14); + ngtcp2_pmtud_probe_sent(pmtud, 2, 14); + ngtcp2_pmtud_handle_expiry(pmtud, 20); + + CU_ASSERT(1492 - 48 == pmtud->min_fail_udp_payload_size); + CU_ASSERT(ngtcp2_pmtud_finished(pmtud)); + + ngtcp2_pmtud_del(pmtud); + + /* Failing 2nd probe should skip the third probe */ + rv = ngtcp2_pmtud_new(&pmtud, NGTCP2_MAX_UDP_PAYLOAD_SIZE, 1452, 0, mem); + + ngtcp2_pmtud_probe_sent(pmtud, 2, 0); + ngtcp2_pmtud_handle_expiry(pmtud, 2); + ngtcp2_pmtud_probe_sent(pmtud, 2, 2); + ngtcp2_pmtud_handle_expiry(pmtud, 4); + ngtcp2_pmtud_probe_sent(pmtud, 2, 4); + ngtcp2_pmtud_handle_expiry(pmtud, 10); + + CU_ASSERT(1454 - 48 == pmtud->min_fail_udp_payload_size); + CU_ASSERT(1 == pmtud->mtu_idx); + + ngtcp2_pmtud_probe_sent(pmtud, 2, 10); + ngtcp2_pmtud_handle_expiry(pmtud, 12); + ngtcp2_pmtud_probe_sent(pmtud, 2, 12); + ngtcp2_pmtud_handle_expiry(pmtud, 14); + ngtcp2_pmtud_probe_sent(pmtud, 2, 14); + ngtcp2_pmtud_handle_expiry(pmtud, 20); + + CU_ASSERT(1390 - 48 == pmtud->min_fail_udp_payload_size); + CU_ASSERT(2 == pmtud->mtu_idx); + + ngtcp2_pmtud_probe_sent(pmtud, 2, 10); + ngtcp2_pmtud_probe_success(pmtud, 1280 - 48); + + CU_ASSERT(ngtcp2_pmtud_finished(pmtud)); + + ngtcp2_pmtud_del(pmtud); + + /* Skip 1st probe because it is larger than hard max. */ + rv = ngtcp2_pmtud_new(&pmtud, NGTCP2_MAX_UDP_PAYLOAD_SIZE, 1454 - 48 - 1, 0, + mem); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == pmtud->mtu_idx); + + ngtcp2_pmtud_del(pmtud); + + /* PMTUD finishes immediately because we know that all candidates + are lower than the current maximum. */ + rv = ngtcp2_pmtud_new(&pmtud, 1492 - 48, 1452, 0, mem); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_pmtud_finished(pmtud)); + + ngtcp2_pmtud_del(pmtud); + + /* PMTUD finishes immediately because the hard maximum size is lower + than the candidates. */ + rv = ngtcp2_pmtud_new(&pmtud, NGTCP2_MAX_UDP_PAYLOAD_SIZE, + NGTCP2_MAX_UDP_PAYLOAD_SIZE, 0, mem); + + CU_ASSERT(0 == rv); + CU_ASSERT(ngtcp2_pmtud_finished(pmtud)); + + ngtcp2_pmtud_del(pmtud); +} diff --git a/tests/ngtcp2_pmtud_test.h b/tests/ngtcp2_pmtud_test.h new file mode 100644 index 0000000..c4e802e --- /dev/null +++ b/tests/ngtcp2_pmtud_test.h @@ -0,0 +1,34 @@ +/* + * ngtcp2 + * + * Copyright (c) 2022 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_PMTUD_TEST_H +#define NGTCP2_PMTUD_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_pmtud_probe(void); + +#endif /* NGTCP2_PMTUD_TEST_H */ diff --git a/tests/ngtcp2_pv_test.c b/tests/ngtcp2_pv_test.c new file mode 100644 index 0000000..6683df1 --- /dev/null +++ b/tests/ngtcp2_pv_test.c @@ -0,0 +1,129 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_pv_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_pv.h" +#include "ngtcp2_test_helper.h" + +void test_ngtcp2_pv_add_entry(void) { + ngtcp2_pv *pv; + int rv; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_cid cid; + const uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN] = {0xff}; + ngtcp2_dcid dcid; + ngtcp2_log log; + uint8_t data[8]; + size_t i; + ngtcp2_duration timeout = 100ULL * NGTCP2_SECONDS; + + dcid_init(&cid); + ngtcp2_dcid_init(&dcid, 1000000007, &cid, token); + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + + rv = ngtcp2_pv_new(&pv, &dcid, timeout, NGTCP2_PV_FLAG_NONE, &log, mem); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == ngtcp2_pv_validation_timed_out(pv, 0)); + + ngtcp2_pv_handle_entry_expiry(pv, 0); + + CU_ASSERT(NGTCP2_PV_NUM_PROBE_PKT == pv->probe_pkt_left); + CU_ASSERT(ngtcp2_pv_should_send_probe(pv)); + + for (i = 0; i < NGTCP2_PV_NUM_PROBE_PKT; ++i) { + ngtcp2_pv_add_entry(pv, data, 100, NGTCP2_PV_ENTRY_FLAG_NONE, 0); + + CU_ASSERT(i + 1 == ngtcp2_ringbuf_len(&pv->ents.rb)); + } + + CU_ASSERT(0 == pv->probe_pkt_left); + CU_ASSERT(!ngtcp2_pv_should_send_probe(pv)); + CU_ASSERT(NGTCP2_PV_NUM_PROBE_PKT == ngtcp2_ringbuf_len(&pv->ents.rb)); + CU_ASSERT(100 == ngtcp2_pv_next_expiry(pv)); + + ngtcp2_pv_handle_entry_expiry(pv, 99); + + CU_ASSERT(0 == pv->probe_pkt_left); + CU_ASSERT(!ngtcp2_pv_should_send_probe(pv)); + + ngtcp2_pv_handle_entry_expiry(pv, 100); + + CU_ASSERT(2 == pv->probe_pkt_left); + CU_ASSERT(ngtcp2_pv_should_send_probe(pv)); + CU_ASSERT(100 == ngtcp2_pv_next_expiry(pv)); + + ngtcp2_pv_add_entry(pv, data, 111, NGTCP2_PV_ENTRY_FLAG_NONE, 100); + + CU_ASSERT(1 == pv->probe_pkt_left); + CU_ASSERT(ngtcp2_pv_should_send_probe(pv)); + CU_ASSERT(111 == ngtcp2_pv_next_expiry(pv)); + + ngtcp2_pv_del(pv); +} + +void test_ngtcp2_pv_validate(void) { + ngtcp2_pv *pv; + int rv; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_cid cid; + const uint8_t token[NGTCP2_STATELESS_RESET_TOKENLEN] = {0xff}; + ngtcp2_dcid dcid; + ngtcp2_log log; + uint8_t data[8]; + ngtcp2_duration timeout = 100ULL * NGTCP2_SECONDS; + ngtcp2_path_storage path; + uint8_t flags; + + path_init(&path, 1, 0, 2, 0); + dcid_init(&cid); + ngtcp2_dcid_init(&dcid, 1000000007, &cid, token); + ngtcp2_path_copy(&dcid.ps.path, &path.path); + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + + rv = ngtcp2_pv_new(&pv, &dcid, timeout, NGTCP2_PV_FLAG_NONE, &log, mem); + + CU_ASSERT(0 == rv); + + memset(data, 0, sizeof(data)); + ngtcp2_pv_add_entry(pv, data, 100, NGTCP2_PV_ENTRY_FLAG_NONE, 1); + + memset(data, 1, sizeof(data)); + ngtcp2_pv_add_entry(pv, data, 100, NGTCP2_PV_ENTRY_FLAG_NONE, 1); + + memset(data, 1, sizeof(data)); + rv = ngtcp2_pv_validate(pv, &flags, data); + + CU_ASSERT(0 == rv); + + memset(data, 3, sizeof(data)); + rv = ngtcp2_pv_validate(pv, &flags, data); + + CU_ASSERT(NGTCP2_ERR_INVALID_ARGUMENT == rv); + + ngtcp2_pv_del(pv); +} diff --git a/tests/ngtcp2_pv_test.h b/tests/ngtcp2_pv_test.h new file mode 100644 index 0000000..331dc09 --- /dev/null +++ b/tests/ngtcp2_pv_test.h @@ -0,0 +1,35 @@ +/* + * ngtcp2 + * + * Copyright (c) 2019 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_PV_TEST_H +#define NGTCP2_PV_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_pv_add_entry(void); +void test_ngtcp2_pv_validate(void); + +#endif /* NGTCP2_PV_TEST_H */ diff --git a/tests/ngtcp2_range_test.c b/tests/ngtcp2_range_test.c new file mode 100644 index 0000000..d60c343 --- /dev/null +++ b/tests/ngtcp2_range_test.c @@ -0,0 +1,105 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_range_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_range.h" +#include "ngtcp2_test_helper.h" + +void test_ngtcp2_range_intersect(void) { + ngtcp2_range a, b, c; + + ngtcp2_range_init(&a, 0, UINT64_MAX); + ngtcp2_range_init(&b, 0, 1000000007); + c = ngtcp2_range_intersect(&a, &b); + + CU_ASSERT(0 == c.begin); + CU_ASSERT(1000000007 == c.end); + + ngtcp2_range_init(&a, 1000000007, UINT64_MAX); + ngtcp2_range_init(&b, 0, UINT64_MAX); + c = ngtcp2_range_intersect(&a, &b); + + CU_ASSERT(1000000007 == c.begin); + CU_ASSERT(UINT64_MAX == c.end); + + ngtcp2_range_init(&a, 0, UINT64_MAX); + ngtcp2_range_init(&b, 33333, 55555); + c = ngtcp2_range_intersect(&a, &b); + + CU_ASSERT(33333 == c.begin); + CU_ASSERT(55555 == c.end); + + ngtcp2_range_init(&a, 0, 1000000009); + ngtcp2_range_init(&b, 1000000007, UINT64_MAX); + c = ngtcp2_range_intersect(&a, &b); + + CU_ASSERT(1000000007 == c.begin); + CU_ASSERT(1000000009 == c.end); +} + +void test_ngtcp2_range_cut(void) { + ngtcp2_range a, b, l, r; + + ngtcp2_range_init(&a, 0, UINT64_MAX); + ngtcp2_range_init(&b, 1000000007, 1000000009); + ngtcp2_range_cut(&l, &r, &a, &b); + + CU_ASSERT(0 == l.begin); + CU_ASSERT(1000000007 == l.end); + CU_ASSERT(1000000009 == r.begin); + CU_ASSERT(UINT64_MAX == r.end); + + ngtcp2_range_init(&a, 0, UINT64_MAX); + ngtcp2_range_init(&b, 0, 1000000007); + ngtcp2_range_cut(&l, &r, &a, &b); + + CU_ASSERT(0 == ngtcp2_range_len(&l)); + CU_ASSERT(1000000007 == r.begin); + CU_ASSERT(UINT64_MAX == r.end); + + ngtcp2_range_init(&a, 0, UINT64_MAX); + ngtcp2_range_init(&b, 1000000009, UINT64_MAX); + ngtcp2_range_cut(&l, &r, &a, &b); + + CU_ASSERT(0 == l.begin); + CU_ASSERT(1000000009 == l.end); + CU_ASSERT(0 == ngtcp2_range_len(&r)); +} + +void test_ngtcp2_range_not_after(void) { + ngtcp2_range a, b; + + ngtcp2_range_init(&a, 1, 1000000007); + ngtcp2_range_init(&b, 0, 1000000007); + + CU_ASSERT(ngtcp2_range_not_after(&a, &b)); + + ngtcp2_range_init(&a, 1, 1000000008); + ngtcp2_range_init(&b, 0, 1000000007); + + CU_ASSERT(!ngtcp2_range_not_after(&a, &b)); +} diff --git a/tests/ngtcp2_range_test.h b/tests/ngtcp2_range_test.h new file mode 100644 index 0000000..a8d2d9c --- /dev/null +++ b/tests/ngtcp2_range_test.h @@ -0,0 +1,36 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_RANGE_TEST_H +#define NGTCP2_RANGE_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_range_intersect(void); +void test_ngtcp2_range_cut(void); +void test_ngtcp2_range_not_after(void); + +#endif /* NGTCP2_RANGE_TEST_H */ diff --git a/tests/ngtcp2_ringbuf_test.c b/tests/ngtcp2_ringbuf_test.c new file mode 100644 index 0000000..b6440f5 --- /dev/null +++ b/tests/ngtcp2_ringbuf_test.c @@ -0,0 +1,91 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_ringbuf_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_ringbuf.h" +#include "ngtcp2_test_helper.h" + +typedef struct { + int32_t a; + uint64_t b; +} ints; + +void test_ngtcp2_ringbuf_push_front(void) { + ngtcp2_ringbuf rb; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + size_t i; + + ngtcp2_ringbuf_init(&rb, 64, sizeof(ints), mem); + + for (i = 0; i < 64; ++i) { + ints *p = ngtcp2_ringbuf_push_front(&rb); + p->a = (int32_t)(i + 1); + p->b = (i + 1) * 10; + } + + CU_ASSERT(64 == ngtcp2_ringbuf_len(&rb)); + + for (i = 0; i < 64; ++i) { + ints *p = ngtcp2_ringbuf_get(&rb, i); + CU_ASSERT((int32_t)(64 - i) == p->a); + CU_ASSERT((64 - i) * 10 == p->b); + } + + ngtcp2_ringbuf_push_front(&rb); + + CU_ASSERT(64 == ngtcp2_ringbuf_len(&rb)); + CU_ASSERT((int32_t)64 == ((ints *)ngtcp2_ringbuf_get(&rb, 1))->a); + + ngtcp2_ringbuf_free(&rb); +} + +void test_ngtcp2_ringbuf_pop_front(void) { + ngtcp2_ringbuf rb; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + size_t i; + + ngtcp2_ringbuf_init(&rb, 4, sizeof(ints), mem); + + for (i = 0; i < 5; ++i) { + ints *p = ngtcp2_ringbuf_push_front(&rb); + p->a = (int32_t)i; + } + + CU_ASSERT(4 == ngtcp2_ringbuf_len(&rb)); + + for (i = 4; i >= 1; --i) { + ints *p = ngtcp2_ringbuf_get(&rb, 0); + + CU_ASSERT((int32_t)i == p->a); + + ngtcp2_ringbuf_pop_front(&rb); + } + + CU_ASSERT(0 == ngtcp2_ringbuf_len(&rb)); + + ngtcp2_ringbuf_free(&rb); +} diff --git a/tests/ngtcp2_ringbuf_test.h b/tests/ngtcp2_ringbuf_test.h new file mode 100644 index 0000000..7ad8b44 --- /dev/null +++ b/tests/ngtcp2_ringbuf_test.h @@ -0,0 +1,35 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_RINGBUF_TEST_H +#define NGTCP2_RINGBUF_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_ringbuf_push_front(void); +void test_ngtcp2_ringbuf_pop_front(void); + +#endif /* NGTCP2_RINGBUF_TEST_H */ diff --git a/tests/ngtcp2_rob_test.c b/tests/ngtcp2_rob_test.c new file mode 100644 index 0000000..93a2641 --- /dev/null +++ b/tests/ngtcp2_rob_test.c @@ -0,0 +1,552 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_rob_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_rob.h" +#include "ngtcp2_test_helper.h" +#include "ngtcp2_mem.h" + +void test_ngtcp2_rob_push(void) { + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_rob rob; + int rv; + uint8_t data[256]; + ngtcp2_rob_gap *g; + ngtcp2_ksl_it it; + + /* Check range overlapping */ + ngtcp2_rob_init(&rob, 64, mem); + + rv = ngtcp2_rob_push(&rob, 34567, data, 145); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&rob.gapksl); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(0 == g->range.begin); + CU_ASSERT(34567 == g->range.end); + + ngtcp2_ksl_it_next(&it); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(34567 + 145 == g->range.begin); + CU_ASSERT(UINT64_MAX == g->range.end); + + ngtcp2_ksl_it_next(&it); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + rv = ngtcp2_rob_push(&rob, 34565, data, 1); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&rob.gapksl); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(0 == g->range.begin); + CU_ASSERT(34565 == g->range.end); + + ngtcp2_ksl_it_next(&it); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(34566 == g->range.begin); + CU_ASSERT(34567 == g->range.end); + + rv = ngtcp2_rob_push(&rob, 34563, data, 1); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&rob.gapksl); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(0 == g->range.begin); + CU_ASSERT(34563 == g->range.end); + + ngtcp2_ksl_it_next(&it); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(34564 == g->range.begin); + CU_ASSERT(34565 == g->range.end); + + rv = ngtcp2_rob_push(&rob, 34561, data, 151); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&rob.gapksl); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(0 == g->range.begin); + CU_ASSERT(34561 == g->range.end); + + ngtcp2_ksl_it_next(&it); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(34567 + 145 == g->range.begin); + CU_ASSERT(UINT64_MAX == g->range.end); + + ngtcp2_ksl_it_next(&it); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_rob_free(&rob); + + /* Check removing prefix */ + ngtcp2_rob_init(&rob, 64, mem); + + rv = ngtcp2_rob_push(&rob, 0, data, 123); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&rob.gapksl); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(123 == g->range.begin); + CU_ASSERT(UINT64_MAX == g->range.end); + + ngtcp2_ksl_it_next(&it); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_rob_free(&rob); + + /* Check removing suffix */ + ngtcp2_rob_init(&rob, 64, mem); + + rv = ngtcp2_rob_push(&rob, UINT64_MAX - 123, data, 123); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&rob.gapksl); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(0 == g->range.begin); + CU_ASSERT(UINT64_MAX - 123 == g->range.end); + + ngtcp2_ksl_it_next(&it); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_rob_free(&rob); +} + +static ngtcp2_range randkeys[] = { + {25996, 26260}, {9431, 9555}, {9113, 9417}, {2992, 3408}, + {35761, 36019}, {38891, 39113}, {30074, 30325}, {9525, 9953}, + {31708, 31944}, {24554, 24864}, {13097, 13472}, {47253, 47400}, + {18424, 18742}, {4618, 4889}, {40871, 41076}, {17642, 18068}, + {47496, 47588}, {1226, 1283}, {17904, 18248}, {9221, 9488}, + {8621, 8773}, {27912, 28344}, {5878, 6121}, {37336, 37545}, + {15403, 15557}, {29314, 29450}, {2342, 2595}, {34000, 34356}, + {46428, 46828}, {40624, 40703}, {47014, 47319}, {13353, 13635}, + {14466, 14682}, {22446, 22654}, {10035, 10140}, {1005, 1410}, + {3741, 4133}, {45734, 46053}, {7954, 8214}, {32666, 32796}, + {45236, 45531}, {32100, 32501}, {25466, 25850}, {2845, 3179}, + {23525, 23991}, {46367, 46459}, {37712, 38164}, {8506, 8680}, + {31702, 31752}, {33364, 33825}, {14284, 14614}, {22928, 23344}, + {29058, 29155}, {36639, 37014}, {29133, 29445}, {31071, 31478}, + {40074, 40370}, {1263, 1383}, {7908, 8181}, {40426, 40716}, + {4830, 5053}, {38241, 38645}, {51197, 51401}, {36180, 36301}, + {14920, 15262}, {5707, 5882}, {32697, 32948}, {42324, 42791}, + {1543, 1732}, {11037, 11395}, {36534, 36707}, {26093, 26322}, + {41862, 42213}, {1373, 1745}, {31322, 31706}, {45474, 45851}, + {19333, 19701}, {49172, 49524}, {10641, 10932}, {17459, 17630}, + {5560, 5936}, {7657, 7988}, {3300, 3357}, {2496, 2600}, + {46018, 46173}, {43127, 43239}, {48949, 49036}, {45094, 45412}, + {8405, 8738}, {8687, 9168}, {41405, 41759}, {22014, 22474}, + {16097, 16426}, {29611, 29931}, {46054, 46250}, {26305, 26545}, + {13696, 13964}, {26899, 26981}, {30797, 30936}, {34125, 34235}, + {50016, 50058}, {46775, 47005}, {4891, 5106}, {12720, 12994}, + {44623, 44967}, {33597, 34060}, {50796, 51295}, {18862, 19242}, + {36166, 36249}, {22237, 22583}, {18188, 18586}, {21376, 21447}, + {49563, 49800}, {10121, 10272}, {39156, 39275}, {17609, 17866}, + {47609, 47829}, {34311, 34631}, {2144, 2433}, {34692, 34824}, + {8309, 8476}, {26969, 27447}, {40651, 40952}, {11906, 12116}, + {22467, 22864}, {35535, 35941}, {33061, 33259}, {21006, 21364}, + {15212, 15504}, {6954, 7356}, {6126, 6405}, {29268, 29514}, + {35221, 35505}, {4163, 4350}, {17374, 17519}, {16170, 16511}, + {37142, 37440}, {6288, 6556}, {27795, 28092}, {35381, 35476}, + {1186, 1455}, {39834, 40197}, {3471, 3906}, {46871, 47242}, + {40258, 40406}, {0, 306}, {31852, 32133}, {23314, 23408}, + {37494, 37625}, {48742, 48990}, {37616, 37905}, {18615, 18991}, + {2561, 2921}, {47767, 48139}, {39616, 39792}, {44791, 45046}, + {2770, 3067}, {16697, 17083}, {9216, 9427}, {37661, 37774}, + {14666, 14976}, {31547, 31819}, {36052, 36356}, {34989, 35285}, + {1651, 2028}, {36264, 36515}, {10257, 10551}, {24381, 24628}, + {28428, 28726}, {4242, 4576}, {44972, 45107}, {12970, 13213}, + {19539, 19828}, {42541, 42763}, {20349, 20630}, {20138, 20418}, + {10884, 11138}, {2717, 2908}, {8292, 8399}, {712, 1101}, + {44451, 44741}, {28660, 28946}, {40955, 41253}, {29424, 29864}, + {14177, 14446}, {30219, 30632}, {24757, 25012}, {47991, 48306}, + {42054, 42252}, {3984, 4419}, {42304, 42506}, {7160, 7543}, + {2004, 2152}, {9777, 10105}, {15724, 16008}, {11263, 11573}, + {15066, 15239}, {12108, 12336}, {17138, 17570}, {30472, 30714}, + {41197, 41294}, {24294, 24496}, {17371, 17514}, {11426, 11749}, + {25223, 25474}, {18083, 18345}, {27611, 27919}, {8116, 8261}, + {40317, 40373}, {46652, 47026}, {18082, 18151}, {19808, 19970}, + {46627, 46885}, {11646, 11789}, {1498, 1687}, {35907, 36081}, + {36340, 36593}, {1255, 1311}, {43485, 43551}, {6586, 6895}, + {10331, 10467}, {26803, 26998}, {14007, 14360}, {35951, 36120}, + {37327, 37592}, {35419, 35724}, {50379, 50514}, {37251, 37489}, + {27313, 27752}, {27502, 27845}, {36608, 36732}, {41751, 42057}, + {19118, 19267}, {16529, 16926}, {49794, 50066}, {37378, 37699}, + {7440, 7552}, {10418, 10650}, {50184, 50635}, {44350, 44579}, + {8178, 8502}, {33838, 34017}, {11582, 11864}, {11756, 11785}, + {42136, 42328}, {39404, 39545}, {13924, 14209}, {29411, 29627}, + {10836, 11139}, {40332, 40598}, {26097, 26561}, {5422, 5512}, + {30687, 30849}, {4399, 4726}, {50679, 50762}, {41224, 41439}, + {46023, 46129}, {22690, 23010}, {37920, 38085}, {25885, 26249}, + {51047, 51185}, {21508, 21904}, {6731, 7010}, {38144, 38493}, + {47648, 47886}, {120, 603}, {49964, 50182}, {43503, 43765}, + {24092, 24436}, {19204, 19509}, {19668, 19930}, {6815, 6963}, + {10552, 10775}, {949, 1239}, {36976, 37348}, {34806, 34901}, + {19939, 20308}, {42245, 42329}, {42700, 43067}, {13821, 14054}, + {28109, 28331}, {32929, 33212}, {23736, 24036}, {31969, 32240}, + {12326, 12612}, {5999, 6132}, {42871, 43283}, {33204, 33496}, + {5757, 5991}, {46826, 46927}, {4994, 5278}, {47371, 47713}, + {20886, 21106}, {38457, 38794}, {48451, 48789}, {34146, 34343}, + {45911, 46248}, {48215, 48615}, {43970, 44131}, {30886, 31216}, + {50135, 50292}, {3726, 3854}, {39041, 39408}, {48617, 48756}, + {46205, 46590}, {39766, 39923}, {20835, 21106}, {43716, 44066}, + {45665, 45789}, {12549, 12755}, {23366, 23752}, {17864, 17942}, + {28288, 28528}, {2744, 2941}, {49355, 49605}, {34527, 34816}, + {23092, 23447}, {5832, 5912}, {21146, 21478}, {30784, 30884}, + {28221, 28469}, {34944, 35047}, {23956, 24126}, {7538, 7890}, + {32496, 32803}, {16404, 16607}, {37968, 38277}, {7399, 7574}, + {28605, 28842}, {50454, 50851}, {20581, 20845}, {21395, 21705}, + {50726, 50871}, {11953, 12278}, {533, 822}, {5298, 5658}, + {48707, 48914}, {21760, 22223}, {1889, 2146}, {6409, 6842}, + {44094, 44473}, {18003, 18336}, {41550, 41926}, {50042, 50136}, + {38646, 38835}, {5425, 5693}, {48967, 49383}, {376, 596}, + {47514, 47704}, {43238, 43663}, {25440, 25655}, {25652, 26050}, + {16909, 17232}, {41312, 41490}, {5909, 6049}, {3153, 3523}, + {27877, 28046}, {26715, 26810}, {10031, 10108}, {32282, 32620}, + {8934, 9219}, {5133, 5493}, {26666, 26787}, {45324, 45630}, + {34880, 35008}, {20823, 20920}, {39571, 39704}, {15523, 15869}, + {4360, 4637}, {46199, 46384}, {35991, 36242}, {46852, 46931}, + {39218, 39644}, {11785, 12029}, {27225, 27366}, {29820, 30097}, + {36778, 37072}, {9871, 10255}, {51065, 51208}, {38775, 39102}, + {39446, 39712}, {33856, 34083}, {28853, 29289}, {526, 666}, + {37510, 37697}, {13455, 13855}, {25648, 25691}, {10694, 11041}, + {26441, 26889}, {18821, 19058}, {3357, 3590}, {15915, 16276}, + {37706, 37934}, {24970, 25281}, {43951, 44124}, {35874, 36128}, +}; + +void test_ngtcp2_rob_push_random(void) { + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_rob rob; + int rv; + uint8_t data[512]; + size_t i; + + ngtcp2_rob_init(&rob, 1024 * 1024, mem); + for (i = 0; i < ngtcp2_arraylen(randkeys); ++i) { + rv = ngtcp2_rob_push(&rob, randkeys[i].begin, &data[0], + (size_t)ngtcp2_range_len(&randkeys[i])); + + CU_ASSERT(0 == rv); + } + + CU_ASSERT(51401 == ngtcp2_rob_first_gap_offset(&rob)); + + ngtcp2_rob_free(&rob); +} + +void test_ngtcp2_rob_data_at(void) { + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_rob rob; + int rv; + uint8_t data[256]; + size_t i; + const uint8_t *p; + size_t len; + ngtcp2_rob_data *d; + ngtcp2_ksl_it it; + ngtcp2_rob_gap *g; + + for (i = 0; i < sizeof(data); ++i) { + data[i] = (uint8_t)i; + } + + ngtcp2_rob_init(&rob, 16, mem); + + rv = ngtcp2_rob_push(&rob, 3, &data[3], 13); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 0); + + CU_ASSERT(0 == len); + + rv = ngtcp2_rob_push(&rob, 0, &data[0], 3); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 0); + + CU_ASSERT(16 == len); + + for (i = 0; i < len; ++i) { + CU_ASSERT((uint8_t)i == *(p + i)); + } + + ngtcp2_rob_pop(&rob, 0, len); + + rv = ngtcp2_rob_push(&rob, 16, &data[16], 5); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 16); + + CU_ASSERT(5 == len); + + for (i = 16; i < len; ++i) { + CU_ASSERT((uint8_t)i == *(p + i)); + } + + ngtcp2_rob_free(&rob); + + /* Verify the case where data spans over multiple chunks */ + ngtcp2_rob_init(&rob, 16, mem); + + rv = ngtcp2_rob_push(&rob, 0, &data[0], 47); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 0); + + CU_ASSERT(16 == len); + + ngtcp2_rob_pop(&rob, 0, len); + len = ngtcp2_rob_data_at(&rob, &p, 16); + + CU_ASSERT(16 == len); + + ngtcp2_rob_pop(&rob, 16, len); + len = ngtcp2_rob_data_at(&rob, &p, 32); + + CU_ASSERT(15 == len); + + ngtcp2_rob_pop(&rob, 32, len); + ngtcp2_rob_free(&rob); + + /* Verify the case where new offset comes before the existing + chunk */ + ngtcp2_rob_init(&rob, 16, mem); + + rv = ngtcp2_rob_push(&rob, 17, &data[17], 2); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 0); + + CU_ASSERT(0 == len); + + rv = ngtcp2_rob_push(&rob, 0, &data[0], 3); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 0); + + CU_ASSERT(3 == len); + + ngtcp2_rob_pop(&rob, 0, len); + + len = ngtcp2_rob_data_at(&rob, &p, 3); + + CU_ASSERT(0 == len); + + ngtcp2_rob_free(&rob); + + /* Verify the case where new offset comes after the existing + chunk */ + ngtcp2_rob_init(&rob, 16, mem); + + rv = ngtcp2_rob_push(&rob, 0, &data[0], 3); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_rob_push(&rob, 16, &data[16], 32); + + CU_ASSERT(0 == rv); + + it = ngtcp2_ksl_begin(&rob.dataksl); + ngtcp2_ksl_it_next(&it); + d = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(16 == d->range.begin); + + ngtcp2_ksl_it_next(&it); + d = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(32 == d->range.begin); + + ngtcp2_ksl_it_next(&it); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_rob_free(&rob); + + /* Severely scattered data */ + ngtcp2_rob_init(&rob, 16, mem); + + for (i = 0; i < sizeof(data); i += 2) { + rv = ngtcp2_rob_push(&rob, i, &data[i], 1); + + CU_ASSERT(0 == rv); + } + + for (i = 1; i < sizeof(data); i += 2) { + rv = ngtcp2_rob_push(&rob, i, &data[i], 1); + + CU_ASSERT(0 == rv); + } + + for (i = 0; i < sizeof(data) / 16; ++i) { + len = ngtcp2_rob_data_at(&rob, &p, i * 16); + + CU_ASSERT(16 == len); + + ngtcp2_rob_pop(&rob, i * 16, len); + } + + it = ngtcp2_ksl_begin(&rob.gapksl); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(256 == g->range.begin); + + it = ngtcp2_ksl_begin(&rob.dataksl); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_rob_free(&rob); + + /* Verify the case where chunk is reused if it is not fully used */ + ngtcp2_rob_init(&rob, 16, mem); + + rv = ngtcp2_rob_push(&rob, 0, &data[0], 5); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 0); + + CU_ASSERT(5 == len); + + ngtcp2_rob_pop(&rob, 0, len); + + rv = ngtcp2_rob_push(&rob, 2, &data[2], 8); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 5); + + CU_ASSERT(5 == len); + + ngtcp2_rob_pop(&rob, 5, len); + + ngtcp2_rob_free(&rob); + + /* Verify the case where 2nd push covers already processed region */ + ngtcp2_rob_init(&rob, 16, mem); + + rv = ngtcp2_rob_push(&rob, 0, &data[0], 16); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 0); + + CU_ASSERT(16 == len); + + ngtcp2_rob_pop(&rob, 0, len); + + rv = ngtcp2_rob_push(&rob, 0, &data[0], 32); + + CU_ASSERT(0 == rv); + + len = ngtcp2_rob_data_at(&rob, &p, 16); + + CU_ASSERT(16 == len); + + ngtcp2_rob_pop(&rob, 16, len); + + ngtcp2_rob_free(&rob); +} + +void test_ngtcp2_rob_remove_prefix(void) { + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_rob rob; + ngtcp2_rob_gap *g; + ngtcp2_rob_data *d; + ngtcp2_ksl_it it; + uint8_t data[256]; + int rv; + + /* Removing data which spans multiple chunks */ + ngtcp2_rob_init(&rob, 16, mem); + + rv = ngtcp2_rob_push(&rob, 1, &data[1], 32); + + CU_ASSERT(0 == rv); + + ngtcp2_rob_remove_prefix(&rob, 33); + + it = ngtcp2_ksl_begin(&rob.gapksl); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(33 == g->range.begin); + + it = ngtcp2_ksl_begin(&rob.dataksl); + d = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(32 == d->range.begin); + + ngtcp2_rob_free(&rob); + + /* Remove an entire gap */ + ngtcp2_rob_init(&rob, 16, mem); + + rv = ngtcp2_rob_push(&rob, 1, &data[1], 3); + + CU_ASSERT(0 == rv); + + rv = ngtcp2_rob_push(&rob, 5, &data[5], 2); + + CU_ASSERT(0 == rv); + + ngtcp2_rob_remove_prefix(&rob, 16); + + it = ngtcp2_ksl_begin(&rob.gapksl); + g = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(16 == g->range.begin); + + ngtcp2_ksl_it_next(&it); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_rob_free(&rob); +} diff --git a/tests/ngtcp2_rob_test.h b/tests/ngtcp2_rob_test.h new file mode 100644 index 0000000..e1f54b6 --- /dev/null +++ b/tests/ngtcp2_rob_test.h @@ -0,0 +1,37 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_ROB_TEST_H +#define NGTCP2_ROB_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_rob_push(void); +void test_ngtcp2_rob_push_random(void); +void test_ngtcp2_rob_data_at(void); +void test_ngtcp2_rob_remove_prefix(void); + +#endif /* NGTCP2_ROB_TEST_H */ diff --git a/tests/ngtcp2_rtb_test.c b/tests/ngtcp2_rtb_test.c new file mode 100644 index 0000000..7c91c38 --- /dev/null +++ b/tests/ngtcp2_rtb_test.c @@ -0,0 +1,470 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_rtb_test.h" + +#include <assert.h> + +#include <CUnit/CUnit.h> + +#include "ngtcp2_rtb.h" +#include "ngtcp2_test_helper.h" +#include "ngtcp2_mem.h" +#include "ngtcp2_pkt.h" + +static void conn_stat_init(ngtcp2_conn_stat *cstat) { + memset(cstat, 0, sizeof(*cstat)); + cstat->max_tx_udp_payload_size = NGTCP2_MAX_UDP_PAYLOAD_SIZE; +} + +void test_ngtcp2_rtb_add(void) { + ngtcp2_rtb rtb; + ngtcp2_rtb_entry *ent; + int rv; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_pkt_hd hd; + ngtcp2_log log; + ngtcp2_cid dcid; + ngtcp2_ksl_it it; + ngtcp2_conn_stat cstat; + ngtcp2_cc cc; + ngtcp2_strm crypto; + const ngtcp2_pktns_id pktns_id = NGTCP2_PKTNS_ID_HANDSHAKE; + ngtcp2_rst rst; + ngtcp2_objalloc frc_objalloc; + ngtcp2_objalloc rtb_entry_objalloc; + + ngtcp2_objalloc_init(&frc_objalloc, 1024, mem); + ngtcp2_objalloc_init(&rtb_entry_objalloc, 1024, mem); + + ngtcp2_strm_init(&crypto, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + dcid_init(&dcid); + conn_stat_init(&cstat); + ngtcp2_rst_init(&rst); + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + ngtcp2_cc_reno_cc_init(&cc, &log, mem); + ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, + &rtb_entry_objalloc, &frc_objalloc, mem); + + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_1RTT, &dcid, NULL, + 1000000007, 1, NGTCP2_PROTO_VER_V1, 0); + + rv = ngtcp2_rtb_entry_objalloc_new( + &ent, &hd, NULL, 10, 0, NGTCP2_RTB_ENTRY_FLAG_NONE, &rtb_entry_objalloc); + + CU_ASSERT(0 == rv); + + ngtcp2_rtb_add(&rtb, ent, &cstat); + + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_1RTT, &dcid, NULL, + 1000000008, 2, NGTCP2_PROTO_VER_V1, 0); + + rv = ngtcp2_rtb_entry_objalloc_new( + &ent, &hd, NULL, 9, 0, NGTCP2_RTB_ENTRY_FLAG_NONE, &rtb_entry_objalloc); + + CU_ASSERT(0 == rv); + + ngtcp2_rtb_add(&rtb, ent, &cstat); + + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_1RTT, &dcid, NULL, + 1000000009, 4, NGTCP2_PROTO_VER_V1, 0); + + rv = ngtcp2_rtb_entry_objalloc_new( + &ent, &hd, NULL, 11, 0, NGTCP2_RTB_ENTRY_FLAG_NONE, &rtb_entry_objalloc); + + CU_ASSERT(0 == rv); + + ngtcp2_rtb_add(&rtb, ent, &cstat); + + it = ngtcp2_rtb_head(&rtb); + ent = ngtcp2_ksl_it_get(&it); + + /* Check the top of the queue */ + CU_ASSERT(1000000009 == ent->hd.pkt_num); + + ngtcp2_ksl_it_next(&it); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(1000000008 == ent->hd.pkt_num); + + ngtcp2_ksl_it_next(&it); + ent = ngtcp2_ksl_it_get(&it); + + CU_ASSERT(1000000007 == ent->hd.pkt_num); + + ngtcp2_ksl_it_next(&it); + + CU_ASSERT(ngtcp2_ksl_it_end(&it)); + + ngtcp2_rtb_free(&rtb); + ngtcp2_cc_reno_cc_free(&cc, mem); + ngtcp2_strm_free(&crypto); + + ngtcp2_objalloc_free(&rtb_entry_objalloc); + ngtcp2_objalloc_free(&frc_objalloc); +} + +static void add_rtb_entry_range(ngtcp2_rtb *rtb, int64_t base_pkt_num, + size_t len, ngtcp2_conn_stat *cstat, + ngtcp2_objalloc *objalloc) { + ngtcp2_pkt_hd hd; + ngtcp2_rtb_entry *ent; + size_t i; + ngtcp2_cid dcid; + + dcid_init(&dcid); + + for (i = 0; i < len; ++i) { + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_1RTT, &dcid, NULL, + base_pkt_num + (int64_t)i, 1, NGTCP2_PROTO_VER_V1, 0); + ngtcp2_rtb_entry_objalloc_new(&ent, &hd, NULL, 0, 0, + NGTCP2_RTB_ENTRY_FLAG_NONE, objalloc); + ngtcp2_rtb_add(rtb, ent, cstat); + } +} + +static void setup_rtb_fixture(ngtcp2_rtb *rtb, ngtcp2_conn_stat *cstat, + ngtcp2_objalloc *objalloc) { + /* 100, ..., 154 */ + add_rtb_entry_range(rtb, 100, 55, cstat, objalloc); + /* 180, ..., 184 */ + add_rtb_entry_range(rtb, 180, 5, cstat, objalloc); + /* 440, ..., 446 */ + add_rtb_entry_range(rtb, 440, 7, cstat, objalloc); +} + +static void assert_rtb_entry_not_found(ngtcp2_rtb *rtb, int64_t pkt_num) { + ngtcp2_ksl_it it = ngtcp2_rtb_head(rtb); + ngtcp2_rtb_entry *ent; + + for (; !ngtcp2_ksl_it_end(&it); ngtcp2_ksl_it_next(&it)) { + ent = ngtcp2_ksl_it_get(&it); + CU_ASSERT(ent->hd.pkt_num != pkt_num); + } +} + +void test_ngtcp2_rtb_recv_ack(void) { + ngtcp2_rtb rtb; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_max_frame mfr; + ngtcp2_ack *fr = &mfr.ackfr.ack; + ngtcp2_ack_range *ranges; + ngtcp2_log log; + ngtcp2_conn_stat cstat; + ngtcp2_cc cc; + ngtcp2_pkt_hd hd; + ngtcp2_ssize num_acked; + ngtcp2_strm crypto; + const ngtcp2_pktns_id pktns_id = NGTCP2_PKTNS_ID_HANDSHAKE; + ngtcp2_rst rst; + ngtcp2_objalloc frc_objalloc; + ngtcp2_objalloc rtb_entry_objalloc; + + ngtcp2_objalloc_init(&frc_objalloc, 1024, mem); + ngtcp2_objalloc_init(&rtb_entry_objalloc, 1024, mem); + + ngtcp2_strm_init(&crypto, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + ngtcp2_pkt_hd_init(&hd, NGTCP2_PKT_FLAG_NONE, NGTCP2_PKT_1RTT, NULL, NULL, 0, + 1, NGTCP2_PROTO_VER_V1, 0); + + /* no ack block */ + conn_stat_init(&cstat); + ngtcp2_rst_init(&rst); + ngtcp2_cc_reno_cc_init(&cc, &log, mem); + ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, + &rtb_entry_objalloc, &frc_objalloc, mem); + setup_rtb_fixture(&rtb, &cstat, &rtb_entry_objalloc); + + CU_ASSERT(67 == ngtcp2_ksl_len(&rtb.ents)); + + fr->largest_ack = 446; + fr->first_ack_range = 1; + fr->rangecnt = 0; + + num_acked = + ngtcp2_rtb_recv_ack(&rtb, fr, &cstat, NULL, NULL, 1000000009, 1000000009); + + CU_ASSERT(2 == num_acked); + CU_ASSERT(65 == ngtcp2_ksl_len(&rtb.ents)); + assert_rtb_entry_not_found(&rtb, 446); + assert_rtb_entry_not_found(&rtb, 445); + + ngtcp2_rtb_free(&rtb); + ngtcp2_cc_reno_cc_free(&cc, mem); + + /* with ack block */ + conn_stat_init(&cstat); + ngtcp2_cc_reno_cc_init(&cc, &log, mem); + ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, + &rtb_entry_objalloc, &frc_objalloc, mem); + setup_rtb_fixture(&rtb, &cstat, &rtb_entry_objalloc); + + fr->largest_ack = 441; + fr->first_ack_range = 3; /* (441), (440), 439, 438 */ + fr->rangecnt = 2; + ranges = fr->ranges; + ranges[0].gap = 253; + ranges[0].len = 0; /* (183) */ + ranges[1].gap = 1; /* 182, 181 */ + ranges[1].len = 1; /* (180), 179 */ + + num_acked = + ngtcp2_rtb_recv_ack(&rtb, fr, &cstat, NULL, NULL, 1000000009, 1000000009); + + CU_ASSERT(4 == num_acked); + CU_ASSERT(63 == ngtcp2_ksl_len(&rtb.ents)); + CU_ASSERT(441 == rtb.largest_acked_tx_pkt_num); + assert_rtb_entry_not_found(&rtb, 441); + assert_rtb_entry_not_found(&rtb, 440); + assert_rtb_entry_not_found(&rtb, 183); + assert_rtb_entry_not_found(&rtb, 180); + + ngtcp2_rtb_free(&rtb); + ngtcp2_cc_reno_cc_free(&cc, mem); + + /* gap+len points to pkt_num 0 */ + conn_stat_init(&cstat); + ngtcp2_cc_reno_cc_init(&cc, &log, mem); + ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, + &rtb_entry_objalloc, &frc_objalloc, mem); + add_rtb_entry_range(&rtb, 0, 1, &cstat, &rtb_entry_objalloc); + + fr->largest_ack = 250; + fr->first_ack_range = 0; + fr->rangecnt = 1; + fr->ranges[0].gap = 248; + fr->ranges[0].len = 0; + + num_acked = + ngtcp2_rtb_recv_ack(&rtb, fr, &cstat, NULL, NULL, 1000000009, 1000000009); + + CU_ASSERT(1 == num_acked); + assert_rtb_entry_not_found(&rtb, 0); + + ngtcp2_rtb_free(&rtb); + ngtcp2_cc_reno_cc_free(&cc, mem); + + /* pkt_num = 0 (first ack block) */ + conn_stat_init(&cstat); + ngtcp2_cc_reno_cc_init(&cc, &log, mem); + ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, + &rtb_entry_objalloc, &frc_objalloc, mem); + add_rtb_entry_range(&rtb, 0, 1, &cstat, &rtb_entry_objalloc); + + fr->largest_ack = 0; + fr->first_ack_range = 0; + fr->rangecnt = 0; + + num_acked = + ngtcp2_rtb_recv_ack(&rtb, fr, &cstat, NULL, NULL, 1000000009, 1000000009); + + CU_ASSERT(1 == num_acked); + assert_rtb_entry_not_found(&rtb, 0); + + ngtcp2_rtb_free(&rtb); + ngtcp2_cc_reno_cc_free(&cc, mem); + + /* pkt_num = 0 */ + conn_stat_init(&cstat); + ngtcp2_cc_reno_cc_init(&cc, &log, mem); + ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, + &rtb_entry_objalloc, &frc_objalloc, mem); + add_rtb_entry_range(&rtb, 0, 1, &cstat, &rtb_entry_objalloc); + + fr->largest_ack = 2; + fr->first_ack_range = 0; + fr->rangecnt = 1; + fr->ranges[0].gap = 0; + fr->ranges[0].len = 0; + + num_acked = + ngtcp2_rtb_recv_ack(&rtb, fr, &cstat, NULL, NULL, 1000000009, 1000000009); + + CU_ASSERT(1 == num_acked); + assert_rtb_entry_not_found(&rtb, 0); + + ngtcp2_rtb_free(&rtb); + ngtcp2_cc_reno_cc_free(&cc, mem); + ngtcp2_strm_free(&crypto); + + ngtcp2_objalloc_free(&rtb_entry_objalloc); + ngtcp2_objalloc_free(&frc_objalloc); +} + +void test_ngtcp2_rtb_lost_pkt_ts(void) { + ngtcp2_rtb rtb; + const ngtcp2_pktns_id pktns_id = NGTCP2_PKTNS_ID_APPLICATION; + ngtcp2_strm crypto; + ngtcp2_log log; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_cc cc; + ngtcp2_rst rst; + ngtcp2_conn_stat cstat; + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + ngtcp2_objalloc frc_objalloc; + ngtcp2_objalloc rtb_entry_objalloc; + + ngtcp2_objalloc_init(&frc_objalloc, 1024, mem); + ngtcp2_objalloc_init(&rtb_entry_objalloc, 1024, mem); + + ngtcp2_strm_init(&crypto, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + + conn_stat_init(&cstat); + ngtcp2_rst_init(&rst); + ngtcp2_cc_reno_cc_init(&cc, &log, mem); + ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, + &rtb_entry_objalloc, &frc_objalloc, mem); + + add_rtb_entry_range(&rtb, 0, 1, &cstat, &rtb_entry_objalloc); + + CU_ASSERT(UINT64_MAX == ngtcp2_rtb_lost_pkt_ts(&rtb)); + + it = ngtcp2_ksl_end(&rtb.ents); + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + ent->flags |= NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED; + ent->lost_ts = 16777217; + + CU_ASSERT(16777217 == ngtcp2_rtb_lost_pkt_ts(&rtb)); + + ngtcp2_rtb_free(&rtb); + ngtcp2_cc_reno_cc_free(&cc, mem); + ngtcp2_strm_free(&crypto); + + ngtcp2_objalloc_free(&rtb_entry_objalloc); + ngtcp2_objalloc_free(&frc_objalloc); +} + +void test_ngtcp2_rtb_remove_expired_lost_pkt(void) { + ngtcp2_rtb rtb; + const ngtcp2_pktns_id pktns_id = NGTCP2_PKTNS_ID_APPLICATION; + ngtcp2_strm crypto; + ngtcp2_log log; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_cc cc; + ngtcp2_rst rst; + ngtcp2_conn_stat cstat; + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + size_t i; + ngtcp2_objalloc frc_objalloc; + ngtcp2_objalloc rtb_entry_objalloc; + + ngtcp2_objalloc_init(&frc_objalloc, 1024, mem); + ngtcp2_objalloc_init(&rtb_entry_objalloc, 1024, mem); + + ngtcp2_strm_init(&crypto, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + + conn_stat_init(&cstat); + ngtcp2_rst_init(&rst); + ngtcp2_cc_reno_cc_init(&cc, &log, mem); + ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, + &rtb_entry_objalloc, &frc_objalloc, mem); + + add_rtb_entry_range(&rtb, 0, 7, &cstat, &rtb_entry_objalloc); + + it = ngtcp2_ksl_end(&rtb.ents); + + for (i = 0; i < 5; ++i) { + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + ent->flags |= NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED; + ent->lost_ts = 16777217 + i; + } + + ngtcp2_rtb_remove_expired_lost_pkt(&rtb, 1, 16777219); + + CU_ASSERT(5 == ngtcp2_ksl_len(&rtb.ents)); + + ngtcp2_rtb_remove_expired_lost_pkt(&rtb, 1, 16777223); + + CU_ASSERT(2 == ngtcp2_ksl_len(&rtb.ents)); + + ngtcp2_rtb_free(&rtb); + ngtcp2_cc_reno_cc_free(&cc, mem); + ngtcp2_strm_free(&crypto); + + ngtcp2_objalloc_free(&rtb_entry_objalloc); + ngtcp2_objalloc_free(&frc_objalloc); +} + +void test_ngtcp2_rtb_remove_excessive_lost_pkt(void) { + ngtcp2_rtb rtb; + const ngtcp2_pktns_id pktns_id = NGTCP2_PKTNS_ID_APPLICATION; + ngtcp2_strm crypto; + ngtcp2_log log; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_cc cc; + ngtcp2_rst rst; + ngtcp2_conn_stat cstat; + ngtcp2_ksl_it it; + ngtcp2_rtb_entry *ent; + size_t i; + ngtcp2_objalloc frc_objalloc; + ngtcp2_objalloc rtb_entry_objalloc; + + ngtcp2_objalloc_init(&frc_objalloc, 1024, mem); + ngtcp2_objalloc_init(&rtb_entry_objalloc, 1024, mem); + + ngtcp2_strm_init(&crypto, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + ngtcp2_log_init(&log, NULL, NULL, 0, NULL); + + conn_stat_init(&cstat); + ngtcp2_rst_init(&rst); + ngtcp2_cc_reno_cc_init(&cc, &log, mem); + ngtcp2_rtb_init(&rtb, pktns_id, &crypto, &rst, &cc, &log, NULL, + &rtb_entry_objalloc, &frc_objalloc, mem); + + add_rtb_entry_range(&rtb, 0, 7, &cstat, &rtb_entry_objalloc); + + it = ngtcp2_ksl_end(&rtb.ents); + + for (i = 0; i < 5; ++i) { + ngtcp2_ksl_it_prev(&it); + ent = ngtcp2_ksl_it_get(&it); + ent->flags |= NGTCP2_RTB_ENTRY_FLAG_LOST_RETRANSMITTED; + ent->lost_ts = 16777217; + ++rtb.num_lost_pkts; + } + + ngtcp2_rtb_remove_excessive_lost_pkt(&rtb, 2); + + CU_ASSERT(4 == ngtcp2_ksl_len(&rtb.ents)); + + ngtcp2_rtb_free(&rtb); + ngtcp2_cc_reno_cc_free(&cc, mem); + ngtcp2_strm_free(&crypto); + + ngtcp2_objalloc_free(&rtb_entry_objalloc); + ngtcp2_objalloc_free(&frc_objalloc); +} diff --git a/tests/ngtcp2_rtb_test.h b/tests/ngtcp2_rtb_test.h new file mode 100644 index 0000000..f880e1e --- /dev/null +++ b/tests/ngtcp2_rtb_test.h @@ -0,0 +1,38 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_RTB_TEST_H +#define NGTCP2_RTB_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_rtb_add(void); +void test_ngtcp2_rtb_recv_ack(void); +void test_ngtcp2_rtb_lost_pkt_ts(void); +void test_ngtcp2_rtb_remove_expired_lost_pkt(void); +void test_ngtcp2_rtb_remove_excessive_lost_pkt(void); + +#endif /* NGTCP2_RTB_TEST_H */ diff --git a/tests/ngtcp2_str_test.c b/tests/ngtcp2_str_test.c new file mode 100644 index 0000000..3d5619d --- /dev/null +++ b/tests/ngtcp2_str_test.c @@ -0,0 +1,96 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_str_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_str.h" +#include "ngtcp2_test_helper.h" + +void test_ngtcp2_encode_ipv4(void) { + uint8_t buf[16]; + + CU_ASSERT(0 == strcmp("192.168.0.1", + (const char *)ngtcp2_encode_ipv4( + buf, (const uint8_t *)"\xc0\xa8\x00\x01"))); + CU_ASSERT(0 == + strcmp("127.0.0.1", (const char *)ngtcp2_encode_ipv4( + buf, (const uint8_t *)"\x7f\x00\x00\x01"))); +} + +void test_ngtcp2_encode_ipv6(void) { + uint8_t buf[32 + 7 + 1]; + + CU_ASSERT( + 0 == + strcmp("2001:db8::2:1", + (const char *)ngtcp2_encode_ipv6( + buf, (const uint8_t *)"\x20\x01\x0d\xb8\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x02\x00\x01"))); + CU_ASSERT( + 0 == + strcmp("2001:db8:0:1:1:1:1:1", + (const char *)ngtcp2_encode_ipv6( + buf, (const uint8_t *)"\x20\x01\x0d\xb8\x00\x00\x00\x01\x00" + "\x01\x00\x01\x00\x01\x00\x01"))); + CU_ASSERT( + 0 == + strcmp("2001:db8::1:0:0:1", + (const char *)ngtcp2_encode_ipv6( + buf, (const uint8_t *)"\x20\x01\x0d\xb8\x00\x00\x00\x00\x00" + "\x01\x00\x00\x00\x00\x00\x01"))); + CU_ASSERT( + 0 == + strcmp("2001:db8::8:800:200c:417a", + (const char *)ngtcp2_encode_ipv6( + buf, (const uint8_t *)"\x20\x01\x0d\xb8\x00\x00\x00\x00\x00" + "\x08\x08\x00\x20\x0C\x41\x7a"))); + CU_ASSERT( + 0 == + strcmp("ff01::101", + (const char *)ngtcp2_encode_ipv6( + buf, (const uint8_t *)"\xff\x01\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x01\x01"))); + CU_ASSERT( + 0 == + strcmp("::1", + (const char *)ngtcp2_encode_ipv6( + buf, (const uint8_t *)"\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x01"))); + CU_ASSERT( + 0 == + strcmp("::", + (const char *)ngtcp2_encode_ipv6( + buf, (const uint8_t *)"\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00"))); +} + +void test_ngtcp2_get_bytes(void) { + const uint8_t src[] = {'f', 'o', 'o', 'b', 'a', 'r'}; + uint8_t dest[256]; + + CU_ASSERT(src + sizeof(src) == ngtcp2_get_bytes(dest, src, sizeof(src))); + CU_ASSERT(0 == memcmp(src, dest, sizeof(src))); +} diff --git a/tests/ngtcp2_str_test.h b/tests/ngtcp2_str_test.h new file mode 100644 index 0000000..9d67c9f --- /dev/null +++ b/tests/ngtcp2_str_test.h @@ -0,0 +1,36 @@ +/* + * ngtcp2 + * + * Copyright (c) 2020 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_STR_TEST_H +#define NGTCP2_STR_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_encode_ipv4(void); +void test_ngtcp2_encode_ipv6(void); +void test_ngtcp2_get_bytes(void); + +#endif /* NGTCP2_STR_TEST_H */ diff --git a/tests/ngtcp2_strm_test.c b/tests/ngtcp2_strm_test.c new file mode 100644 index 0000000..b2a71c0 --- /dev/null +++ b/tests/ngtcp2_strm_test.c @@ -0,0 +1,575 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_strm_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_strm.h" +#include "ngtcp2_test_helper.h" +#include "ngtcp2_vec.h" + +static uint8_t nulldata[1024]; + +static void setup_strm_streamfrq_fixture(ngtcp2_strm *strm, + ngtcp2_objalloc *frc_objalloc, + const ngtcp2_mem *mem) { + ngtcp2_frame_chain *frc; + ngtcp2_vec *data; + + ngtcp2_strm_init(strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, frc_objalloc, + mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 2, frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 2; + data = frc->fr.stream.data; + data[0].len = 11; + data[0].base = nulldata; + data[1].len = 19; + data[1].base = nulldata + 11; + + ngtcp2_strm_streamfrq_push(strm, frc); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 2, frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 30; + frc->fr.stream.datacnt = 2; + data = frc->fr.stream.data; + data[0].len = 17; + data[0].base = nulldata + 30; + data[1].len = 29; + data[1].base = nulldata + 30 + 17; + + ngtcp2_strm_streamfrq_push(strm, frc); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 2, frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 76; + frc->fr.stream.datacnt = 2; + data = frc->fr.stream.data; + data[0].len = 31; + data[0].base = nulldata + 256; + data[1].len = 1; + data[1].base = nulldata + 512; + + ngtcp2_strm_streamfrq_push(strm, frc); +} + +void test_ngtcp2_strm_streamfrq_pop(void) { + ngtcp2_strm strm; + ngtcp2_frame_chain *frc; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + int rv; + ngtcp2_vec *data; + ngtcp2_objalloc frc_objalloc; + + ngtcp2_objalloc_init(&frc_objalloc, 1024, mem); + + /* Get first chain */ + setup_strm_streamfrq_fixture(&strm, &frc_objalloc, mem); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 30); + + CU_ASSERT(0 == rv); + CU_ASSERT(2 == frc->fr.stream.datacnt); + + data = frc->fr.stream.data; + + CU_ASSERT(11 == data[0].len); + CU_ASSERT(19 == data[1].len); + CU_ASSERT(2 == ngtcp2_ksl_len(strm.tx.streamfrq)); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + /* Get merged chain */ + setup_strm_streamfrq_fixture(&strm, &frc_objalloc, mem); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 76); + + CU_ASSERT(0 == rv); + CU_ASSERT(2 == frc->fr.stream.datacnt); + + data = frc->fr.stream.data; + + CU_ASSERT(11 == data[0].len); + CU_ASSERT(19 + 46 == data[1].len); + CU_ASSERT(1 == ngtcp2_ksl_len(strm.tx.streamfrq)); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + /* Get merged chain partially */ + setup_strm_streamfrq_fixture(&strm, &frc_objalloc, mem); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 75); + + CU_ASSERT(0 == rv); + CU_ASSERT(2 == frc->fr.stream.datacnt); + + data = frc->fr.stream.data; + + CU_ASSERT(11 == data[0].len); + CU_ASSERT(19 + 45 == data[1].len); + CU_ASSERT(2 == ngtcp2_ksl_len(strm.tx.streamfrq)); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1); + + CU_ASSERT(0 == rv); + CU_ASSERT(75 == frc->fr.stream.offset); + CU_ASSERT(1 == frc->fr.stream.datacnt); + CU_ASSERT(1 == frc->fr.stream.data[0].len); + CU_ASSERT(nulldata + 30 + 17 + 28 == frc->fr.stream.data[0].base); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + /* Not continuous merge */ + setup_strm_streamfrq_fixture(&strm, &frc_objalloc, mem); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 77); + + CU_ASSERT(0 == rv); + CU_ASSERT(3 == frc->fr.stream.datacnt); + + data = frc->fr.stream.data; + + CU_ASSERT(11 == data[0].len); + CU_ASSERT(19 + 46 == data[1].len); + CU_ASSERT(1 == data[2].len); + CU_ASSERT(nulldata + 256 == data[2].base); + CU_ASSERT(1 == ngtcp2_ksl_len(strm.tx.streamfrq)); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024); + + CU_ASSERT(0 == rv); + CU_ASSERT(77 == frc->fr.stream.offset); + CU_ASSERT(2 == frc->fr.stream.datacnt); + + data = frc->fr.stream.data; + + CU_ASSERT(30 == data[0].len); + CU_ASSERT(nulldata + 256 + 1 == data[0].base); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + /* split; continuous */ + setup_strm_streamfrq_fixture(&strm, &frc_objalloc, mem); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 12); + + CU_ASSERT(0 == rv); + CU_ASSERT(0 == frc->fr.stream.offset); + CU_ASSERT(2 == frc->fr.stream.datacnt); + + data = frc->fr.stream.data; + + CU_ASSERT(11 == data[0].len); + CU_ASSERT(nulldata == data[0].base); + CU_ASSERT(1 == data[1].len); + CU_ASSERT(nulldata + 11 == data[1].base); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024); + + CU_ASSERT(0 == rv); + CU_ASSERT(12 == frc->fr.stream.offset); + CU_ASSERT(3 == frc->fr.stream.datacnt); + + data = frc->fr.stream.data; + + CU_ASSERT(64 == data[0].len); + CU_ASSERT(nulldata + 12 == data[0].base); + CU_ASSERT(31 == data[1].len); + CU_ASSERT(nulldata + 256 == data[1].base); + CU_ASSERT(1 == data[2].len); + CU_ASSERT(nulldata + 512 == data[2].base); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + /* offset gap */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 11; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 30; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 17; + data[0].base = nulldata + 30; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == frc->fr.stream.datacnt); + CU_ASSERT(11 == frc->fr.stream.data[0].len); + CU_ASSERT(1 == ngtcp2_ksl_len(strm.tx.streamfrq)); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + /* fin */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 11; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 0, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 1; + frc->fr.stream.offset = 11; + frc->fr.stream.datacnt = 0; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024); + + CU_ASSERT(0 == rv); + CU_ASSERT(1 == frc->fr.stream.fin); + CU_ASSERT(1 == frc->fr.stream.datacnt); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + /* left == 0 and there is outstanding data */ + setup_strm_streamfrq_fixture(&strm, &frc_objalloc, mem); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 0); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == frc); + + ngtcp2_strm_free(&strm); + + ngtcp2_objalloc_free(&frc_objalloc); +} + +void test_ngtcp2_strm_streamfrq_unacked_offset(void) { + ngtcp2_strm strm; + ngtcp2_frame_chain *frc; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_vec *data; + ngtcp2_objalloc frc_objalloc; + + ngtcp2_objalloc_init(&frc_objalloc, 1024, mem); + + /* Everything acknowledged including fin */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_FIN_ACKED, 0, 0, NULL, + &frc_objalloc, mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 17; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 1; + frc->fr.stream.offset = 443; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 443 + 971); + + CU_ASSERT((uint64_t)-1 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); + + /* Everything acknowledged but fin */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 17; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 1; + frc->fr.stream.offset = 443; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 443 + 971); + + CU_ASSERT(443 + 971 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); + + /* Unacked gap starts in the middle of stream to resend */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 443); + + CU_ASSERT(443 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); + + /* Unacked gap starts after stream to resend */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 0; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 971); + + CU_ASSERT((uint64_t)-1 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); + + /* Unacked gap and stream overlap and gap starts before stream */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 977; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 971; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 971); + + CU_ASSERT(977 == ngtcp2_strm_streamfrq_unacked_offset(&strm)); + + ngtcp2_strm_free(&strm); + + ngtcp2_objalloc_free(&frc_objalloc); +} + +void test_ngtcp2_strm_streamfrq_unacked_pop(void) { + ngtcp2_strm strm; + ngtcp2_frame_chain *frc; + const ngtcp2_mem *mem = ngtcp2_mem_default(); + ngtcp2_vec *data; + int rv; + ngtcp2_objalloc frc_objalloc; + + ngtcp2_objalloc_init(&frc_objalloc, 1024, mem); + + /* Everything acknowledged including fin */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_FIN_ACKED, 0, 0, NULL, + &frc_objalloc, mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 307; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 149; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 1; + frc->fr.stream.offset = 457; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 307; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 764); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024); + + CU_ASSERT(0 == rv); + CU_ASSERT(NULL == frc); + + ngtcp2_strm_free(&strm); + + /* Everything acknowledged but fin */ + ngtcp2_strm_init(&strm, 0, NGTCP2_STRM_FLAG_NONE, 0, 0, NULL, &frc_objalloc, + mem); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 0; + frc->fr.stream.offset = 307; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 149; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_frame_chain_stream_datacnt_objalloc_new(&frc, 1, &frc_objalloc, mem); + frc->fr.stream.type = NGTCP2_FRAME_STREAM; + frc->fr.stream.fin = 1; + frc->fr.stream.offset = 457; + frc->fr.stream.datacnt = 1; + data = frc->fr.stream.data; + data[0].len = 307; + data[0].base = nulldata; + + ngtcp2_strm_streamfrq_push(&strm, frc); + + ngtcp2_strm_ack_data(&strm, 0, 764); + + frc = NULL; + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 1024); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_FRAME_STREAM == frc->fr.type); + CU_ASSERT(1 == frc->fr.stream.fin); + CU_ASSERT(764 == frc->fr.stream.offset); + CU_ASSERT(0 == ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt)); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + /* Remove leading acknowledged data */ + setup_strm_streamfrq_fixture(&strm, &frc_objalloc, mem); + + ngtcp2_strm_ack_data(&strm, 0, 12); + + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 43); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_FRAME_STREAM == frc->fr.type); + CU_ASSERT(0 == frc->fr.stream.fin); + CU_ASSERT(12 == frc->fr.stream.offset); + CU_ASSERT(1 == frc->fr.stream.datacnt); + CU_ASSERT(43 == ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt)); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + /* Creating a gap of acknowledged data */ + setup_strm_streamfrq_fixture(&strm, &frc_objalloc, mem); + + ngtcp2_strm_ack_data(&strm, 32, 1); + + rv = ngtcp2_strm_streamfrq_pop(&strm, &frc, 43); + + CU_ASSERT(0 == rv); + CU_ASSERT(NGTCP2_FRAME_STREAM == frc->fr.type); + CU_ASSERT(0 == frc->fr.stream.fin); + CU_ASSERT(0 == frc->fr.stream.offset); + CU_ASSERT(2 == frc->fr.stream.datacnt); + CU_ASSERT(32 == ngtcp2_vec_len(frc->fr.stream.data, frc->fr.stream.datacnt)); + + ngtcp2_frame_chain_objalloc_del(frc, &frc_objalloc, mem); + ngtcp2_strm_free(&strm); + + ngtcp2_objalloc_free(&frc_objalloc); +} diff --git a/tests/ngtcp2_strm_test.h b/tests/ngtcp2_strm_test.h new file mode 100644 index 0000000..f94c423 --- /dev/null +++ b/tests/ngtcp2_strm_test.h @@ -0,0 +1,36 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_STRM_TEST_H +#define NGTCP2_STRM_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_strm_streamfrq_pop(void); +void test_ngtcp2_strm_streamfrq_unacked_offset(void); +void test_ngtcp2_strm_streamfrq_unacked_pop(void); + +#endif /* NGTCP2_STRM_TEST_H */ diff --git a/tests/ngtcp2_test_helper.c b/tests/ngtcp2_test_helper.c new file mode 100644 index 0000000..a29a943 --- /dev/null +++ b/tests/ngtcp2_test_helper.c @@ -0,0 +1,404 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_test_helper.h" + +#include <string.h> +#include <assert.h> + +#include "ngtcp2_conv.h" +#include "ngtcp2_pkt.h" +#include "ngtcp2_ppe.h" +#include "ngtcp2_vec.h" +#include "ngtcp2_net.h" + +size_t ngtcp2_t_encode_stream_frame(uint8_t *out, uint8_t flags, + uint64_t stream_id, uint64_t offset, + uint16_t datalen) { + uint8_t *p = out; + + if (offset) { + flags |= NGTCP2_STREAM_OFF_BIT; + } + *p++ = NGTCP2_FRAME_STREAM | flags; + + p = ngtcp2_put_uvarint(p, stream_id); + + if (offset) { + p = ngtcp2_put_uvarint(p, offset); + } + + if (flags & NGTCP2_STREAM_LEN_BIT) { + p = ngtcp2_put_uvarint(p, datalen); + } + + memset(p, 0, datalen); + p += datalen; + + return (size_t)(p - out); +} + +size_t ngtcp2_t_encode_ack_frame(uint8_t *out, uint64_t largest_ack, + uint64_t first_ack_blklen, uint64_t gap, + uint64_t ack_blklen) { + uint8_t *p = out; + + p = out; + + *p++ = NGTCP2_FRAME_ACK; + /* Largest Acknowledged */ + p = ngtcp2_put_uvarint(p, largest_ack); + /* ACK Delay */ + p = ngtcp2_put_uvarint(p, 0); + /* ACK Block Count */ + p = ngtcp2_put_uvarint(p, 1); + /* First ACK Block */ + p = ngtcp2_put_uvarint(p, first_ack_blklen); + /* Gap (1) */ + p = ngtcp2_put_uvarint(p, gap); + /* Additional ACK Block (1) */ + p = ngtcp2_put_uvarint(p, ack_blklen); + + return (size_t)(p - out); +} + +static int null_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, + const ngtcp2_crypto_aead_ctx *aead_ctx, + const uint8_t *plaintext, size_t plaintextlen, + const uint8_t *nonce, size_t noncelen, + const uint8_t *aad, size_t aadlen) { + (void)dest; + (void)aead; + (void)aead_ctx; + (void)plaintext; + (void)plaintextlen; + (void)nonce; + (void)noncelen; + (void)aad; + (void)aadlen; + memset(dest + plaintextlen, 0, NGTCP2_FAKE_AEAD_OVERHEAD); + return 0; +} + +static int null_hp_mask(uint8_t *dest, const ngtcp2_crypto_cipher *hp, + const ngtcp2_crypto_cipher_ctx *hp_ctx, + const uint8_t *sample) { + (void)hp; + (void)hp_ctx; + (void)sample; + memcpy(dest, NGTCP2_FAKE_HP_MASK, sizeof(NGTCP2_FAKE_HP_MASK) - 1); + return 0; +} + +size_t write_pkt(uint8_t *out, size_t outlen, const ngtcp2_cid *dcid, + int64_t pkt_num, ngtcp2_frame *fr, size_t frlen, + ngtcp2_crypto_km *ckm) { + return write_pkt_flags(out, outlen, NGTCP2_PKT_FLAG_NONE, dcid, pkt_num, fr, + frlen, ckm); +} + +size_t write_pkt_flags(uint8_t *out, size_t outlen, uint8_t flags, + const ngtcp2_cid *dcid, int64_t pkt_num, + ngtcp2_frame *fr, size_t frlen, ngtcp2_crypto_km *ckm) { + ngtcp2_crypto_cc cc; + ngtcp2_ppe ppe; + ngtcp2_pkt_hd hd; + int rv; + ngtcp2_ssize n; + size_t i; + + memset(&cc, 0, sizeof(cc)); + cc.encrypt = null_encrypt; + cc.hp_mask = null_hp_mask; + cc.ckm = ckm; + cc.aead.max_overhead = NGTCP2_FAKE_AEAD_OVERHEAD; + + ngtcp2_pkt_hd_init(&hd, flags, NGTCP2_PKT_1RTT, dcid, NULL, pkt_num, 4, + NGTCP2_PROTO_VER_V1, 0); + + ngtcp2_ppe_init(&ppe, out, outlen, &cc); + rv = ngtcp2_ppe_encode_hd(&ppe, &hd); + assert(0 == rv); + + for (i = 0; i < frlen; ++i, ++fr) { + rv = ngtcp2_ppe_encode_frame(&ppe, fr); + assert(0 == rv); + } + + n = ngtcp2_ppe_final(&ppe, NULL); + assert(n > 0); + + return (size_t)n; +} + +static size_t write_long_header_pkt_generic( + uint8_t *out, size_t outlen, uint8_t pkt_type, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, int64_t pkt_num, uint32_t version, + const uint8_t *token, size_t tokenlen, ngtcp2_frame *fr, size_t frlen, + ngtcp2_crypto_km *ckm) { + ngtcp2_crypto_cc cc; + ngtcp2_ppe ppe; + ngtcp2_pkt_hd hd; + int rv; + ngtcp2_ssize n; + size_t i; + + memset(&cc, 0, sizeof(cc)); + cc.encrypt = null_encrypt; + cc.hp_mask = null_hp_mask; + cc.ckm = ckm; + switch (pkt_type) { + case NGTCP2_PKT_INITIAL: + cc.aead.max_overhead = NGTCP2_INITIAL_AEAD_OVERHEAD; + break; + case NGTCP2_PKT_HANDSHAKE: + case NGTCP2_PKT_0RTT: + cc.aead.max_overhead = NGTCP2_FAKE_AEAD_OVERHEAD; + break; + default: + assert(0); + } + + /* ngtcp2_pkt_encode_hd_long requires known QUIC version. If we + need to write unsupported version for testing purpose, just + pretend that it is QUIC v1 here and rewrite the version field + later. */ + ngtcp2_pkt_hd_init( + &hd, NGTCP2_PKT_FLAG_LONG_FORM, pkt_type, dcid, scid, pkt_num, 4, + version != NGTCP2_PROTO_VER_V1 && version != NGTCP2_PROTO_VER_V2_DRAFT + ? NGTCP2_PROTO_VER_V1 + : version, + 0); + + hd.token.base = (uint8_t *)token; + hd.token.len = tokenlen; + + ngtcp2_ppe_init(&ppe, out, outlen, &cc); + rv = ngtcp2_ppe_encode_hd(&ppe, &hd); + assert(0 == rv); + ngtcp2_put_uint32be(&out[1], version); + + for (i = 0; i < frlen; ++i, ++fr) { + rv = ngtcp2_ppe_encode_frame(&ppe, fr); + assert(0 == rv); + } + + n = ngtcp2_ppe_final(&ppe, NULL); + assert(n > 0); + return (size_t)n; +} + +size_t write_initial_pkt(uint8_t *out, size_t outlen, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, int64_t pkt_num, + uint32_t version, const uint8_t *token, + size_t tokenlen, ngtcp2_frame *fr, size_t frlen, + ngtcp2_crypto_km *ckm) { + return write_long_header_pkt_generic(out, outlen, NGTCP2_PKT_INITIAL, dcid, + scid, pkt_num, version, token, tokenlen, + fr, frlen, ckm); +} + +size_t write_handshake_pkt(uint8_t *out, size_t outlen, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, int64_t pkt_num, + uint32_t version, ngtcp2_frame *fr, size_t frlen, + ngtcp2_crypto_km *ckm) { + return write_long_header_pkt_generic(out, outlen, NGTCP2_PKT_HANDSHAKE, dcid, + scid, pkt_num, version, NULL, 0, fr, + frlen, ckm); +} + +size_t write_0rtt_pkt(uint8_t *out, size_t outlen, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, int64_t pkt_num, uint32_t version, + ngtcp2_frame *fr, size_t frlen, ngtcp2_crypto_km *ckm) { + return write_long_header_pkt_generic(out, outlen, NGTCP2_PKT_0RTT, dcid, scid, + pkt_num, version, NULL, 0, fr, frlen, + ckm); +} + +ngtcp2_strm *open_stream(ngtcp2_conn *conn, int64_t stream_id) { + ngtcp2_strm *strm; + int rv; + (void)rv; + + strm = ngtcp2_objalloc_strm_get(&conn->strm_objalloc); + assert(strm); + + rv = ngtcp2_conn_init_stream(conn, strm, stream_id, NULL); + assert(0 == rv); + + return strm; +} + +size_t rtb_entry_length(const ngtcp2_rtb_entry *ent) { + size_t len = 0; + + for (; ent; ent = ent->next) { + ++len; + } + + return len; +} + +void dcid_init(ngtcp2_cid *cid) { + static const uint8_t id[] = "\xff\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xff"; + ngtcp2_cid_init(cid, id, sizeof(id) - 1); +} + +void scid_init(ngtcp2_cid *cid) { + static const uint8_t id[] = "\xee\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xee"; + ngtcp2_cid_init(cid, id, sizeof(id) - 1); +} + +void rcid_init(ngtcp2_cid *cid) { + static const uint8_t id[] = "\xdd\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xdd"; + ngtcp2_cid_init(cid, id, sizeof(id) - 1); +} + +uint64_t read_pkt_payloadlen(const uint8_t *pkt, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid) { + uint64_t len; + + ngtcp2_get_uvarint(&len, &pkt[1 + 4 + 1 + dcid->datalen + 1 + scid->datalen]); + + return len; +} + +void write_pkt_payloadlen(uint8_t *pkt, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, uint64_t payloadlen) { + assert(payloadlen < 1073741824); + ngtcp2_put_uvarint30(&pkt[1 + 4 + 1 + dcid->datalen + 1 + scid->datalen], + (uint32_t)payloadlen); +} + +ngtcp2_ssize pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen) { + const uint8_t *p; + ngtcp2_ssize nread; + + nread = ngtcp2_pkt_decode_hd_long(dest, pkt, pktlen); + if (nread < 0 || (!(dest->flags & NGTCP2_PKT_FLAG_LONG_FORM) && + dest->type == NGTCP2_PKT_VERSION_NEGOTIATION)) { + return nread; + } + + if ((size_t)nread == pktlen) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p = pkt + nread; + + dest->pkt_numlen = (size_t)(pkt[0] & NGTCP2_PKT_NUMLEN_MASK) + 1; + if (pktlen < (size_t)nread + dest->pkt_numlen) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dest->pkt_num = ngtcp2_get_pkt_num(p, dest->pkt_numlen); + + return nread + (ngtcp2_ssize)dest->pkt_numlen; +} + +ngtcp2_ssize pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen, size_t dcidlen) { + const uint8_t *p; + ngtcp2_ssize nread; + + nread = ngtcp2_pkt_decode_hd_short(dest, pkt, pktlen, dcidlen); + if (nread < 0) { + return nread; + } + + if ((size_t)nread == pktlen) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p = pkt + nread; + + dest->pkt_numlen = (size_t)(pkt[0] & NGTCP2_PKT_NUMLEN_MASK) + 1; + if (pktlen < (size_t)nread + dest->pkt_numlen) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + dest->pkt_num = ngtcp2_get_pkt_num(p, dest->pkt_numlen); + + return nread + (ngtcp2_ssize)dest->pkt_numlen; +} + +ngtcp2_ssize pkt_decode_hd_short_mask(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen, size_t dcidlen) { + static const uint8_t mask[] = NGTCP2_FAKE_HP_MASK; + const uint8_t *p; + ngtcp2_ssize nread; + uint8_t hb; + uint8_t pkt_numbuf[4]; + size_t i; + + nread = ngtcp2_pkt_decode_hd_short(dest, pkt, pktlen, dcidlen); + if (nread < 0) { + return nread; + } + + if ((size_t)nread == pktlen) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + p = pkt + nread; + + hb = (uint8_t)(pkt[0] ^ (mask[0] & 0x1f)); + + dest->pkt_numlen = (size_t)(hb & NGTCP2_PKT_NUMLEN_MASK) + 1; + if (pktlen < (size_t)nread + dest->pkt_numlen) { + return NGTCP2_ERR_INVALID_ARGUMENT; + } + + for (i = 0; i < dest->pkt_numlen; ++i) { + pkt_numbuf[i] = *(p + i) ^ mask[i + 1]; + } + + dest->pkt_num = ngtcp2_get_pkt_num(pkt_numbuf, dest->pkt_numlen); + + return nread + (ngtcp2_ssize)dest->pkt_numlen; +} + +static void addr_init(ngtcp2_sockaddr_in *dest, uint32_t addr, uint16_t port) { + memset(dest, 0, sizeof(*dest)); + + dest->sin_family = AF_INET; + dest->sin_port = ngtcp2_htons(port); + dest->sin_addr.s_addr = ngtcp2_htonl(addr); +} + +void path_init(ngtcp2_path_storage *path, uint32_t local_addr, + uint16_t local_port, uint32_t remote_addr, + uint16_t remote_port) { + ngtcp2_sockaddr_in la, ra; + + addr_init(&la, local_addr, local_port); + addr_init(&ra, remote_addr, remote_port); + + ngtcp2_path_storage_init(path, (ngtcp2_sockaddr *)&la, sizeof(la), + (ngtcp2_sockaddr *)&ra, sizeof(ra), NULL); +} diff --git a/tests/ngtcp2_test_helper.h b/tests/ngtcp2_test_helper.h new file mode 100644 index 0000000..894cdf3 --- /dev/null +++ b/tests/ngtcp2_test_helper.h @@ -0,0 +1,191 @@ +/* + * ngtcp2 + * + * Copyright (c) 2017 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_TEST_HELPER_H +#define NGTCP2_TEST_HELPER_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <ngtcp2/ngtcp2.h> + +#include "ngtcp2_conn.h" + +/* + * strsize macro returns the length of string literal |S|. + */ +#define strsize(S) (sizeof(S) - 1) + +/* + * NGTCP2_APP_ERRxx is an application error code solely used in test + * code. + */ +#define NGTCP2_APP_ERR01 0xff01u +#define NGTCP2_APP_ERR02 0xff02u + +/* + * NGTCP2_FAKE_AEAD_OVERHEAD is AEAD overhead used in unit tests. + * Because we use the same encryption/decryption function for both + * handshake and post handshake packets, we have to use AEAD overhead + * used in handshake packets. + */ +#define NGTCP2_FAKE_AEAD_OVERHEAD NGTCP2_INITIAL_AEAD_OVERHEAD + +/* NGTCP2_FAKE_HP_MASK is a header protection mask used in unit + tests. */ +#define NGTCP2_FAKE_HP_MASK "\x00\x00\x00\x00\x00" + +/* + * ngtcp2_t_encode_stream_frame encodes STREAM frame into |out| with + * the given parameters. If NGTCP2_STREAM_LEN_BIT is set in |flags|, + * |datalen| is encoded as Data Length, otherwise it is not written. + * To set FIN bit in wire format, set NGTCP2_STREAM_FIN_BIT in + * |flags|. This function expects that |out| has enough length to + * store entire STREAM frame, excluding the Stream Data. + * + * This function returns the number of bytes written to |out|. + */ +size_t ngtcp2_t_encode_stream_frame(uint8_t *out, uint8_t flags, + uint64_t stream_id, uint64_t offset, + uint16_t datalen); + +/* + * ngtcp2_t_encode_ack_frame encodes ACK frame into |out| with the + * given parameters. Currently, this function encodes 1 ACK Block + * Section. ACK Delay field is always 0. + * + * This function returns the number of bytes written to |out|. + */ +size_t ngtcp2_t_encode_ack_frame(uint8_t *out, uint64_t largest_ack, + uint64_t first_ack_blklen, uint64_t gap, + uint64_t ack_blklen); + +/* + * write_pkt_flags writes a QUIC packet containing frames pointed by + * |fr| of length |frlen| in |out| whose capacity is |outlen|. This + * function returns the number of bytes written. + */ +size_t write_pkt_flags(uint8_t *out, size_t outlen, uint8_t flags, + const ngtcp2_cid *dcid, int64_t pkt_num, + ngtcp2_frame *fr, size_t frlen, ngtcp2_crypto_km *ckm); + +/* + * write_pkt is write_pkt_flags with flag = NGTCP2_PKT_FLAG_NONE. + */ +size_t write_pkt(uint8_t *out, size_t outlen, const ngtcp2_cid *dcid, + int64_t pkt_num, ngtcp2_frame *fr, size_t frlen, + ngtcp2_crypto_km *ckm); + +/* + * write_handshake_pkt writes a QUIC Initial packet containing |frlen| + * frames pointed by |fr| into |out| whose capacity is |outlen|. This + * function returns the number of bytes written. + */ +size_t write_initial_pkt(uint8_t *out, size_t outlen, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, int64_t pkt_num, + uint32_t version, const uint8_t *token, + size_t tokenlen, ngtcp2_frame *fr, size_t frlen, + ngtcp2_crypto_km *ckm); + +/* + * write_handshake_pkt writes a QUIC Handshake packet containing + * |frlen| frames pointed by |fr| into |out| whose capacity is + * |outlen|. This function returns the number of bytes written. + */ +size_t write_handshake_pkt(uint8_t *out, size_t outlen, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, int64_t pkt_num, + uint32_t version, ngtcp2_frame *fr, size_t frlen, + ngtcp2_crypto_km *ckm); + +/* + * write_0rtt_pkt writes a QUIC 0RTT packet containing |frlen| frames + * pointed by |fr| into |out| whose capacity is |outlen|. This + * function returns the number of bytes written. + */ +size_t write_0rtt_pkt(uint8_t *out, size_t outlen, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, int64_t pkt_num, uint32_t version, + ngtcp2_frame *fr, size_t frlen, ngtcp2_crypto_km *ckm); + +/* + * open_stream opens new stream denoted by |stream_id|. + */ +ngtcp2_strm *open_stream(ngtcp2_conn *conn, int64_t stream_id); + +/* + * rtb_entry_length returns the length of elements pointed by |ent| + * list. + */ +size_t rtb_entry_length(const ngtcp2_rtb_entry *ent); + +void scid_init(ngtcp2_cid *cid); +void dcid_init(ngtcp2_cid *cid); +void rcid_init(ngtcp2_cid *cid); + +/* + * read_pkt_payloadlen reads long header payload length field from + * |pkt|. + */ +uint64_t read_pkt_payloadlen(const uint8_t *pkt, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid); + +/* + * write_pkt_payloadlen writes long header payload length field into + * |pkt|. + */ +void write_pkt_payloadlen(uint8_t *pkt, const ngtcp2_cid *dcid, + const ngtcp2_cid *scid, uint64_t payloadlen); + +/* + * pkt_decode_hd_long decodes long packet header from |pkt| of length + * |pktlen|. This function assumes that header protection has been + * decrypted. + */ +ngtcp2_ssize pkt_decode_hd_long(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen); + +/* + * pkt_decode_hd_short decodes long packet header from |pkt| of length + * |pktlen|. This function assumes that header protection has been + * decrypted. + */ +ngtcp2_ssize pkt_decode_hd_short(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen, size_t dcidlen); + +/* + * pkt_decode_hd_short_mask decodes long packet header from |pkt| of + * length |pktlen|. NGTCP2_FAKE_HP_MASK is used to decrypt header + * protection. + */ +ngtcp2_ssize pkt_decode_hd_short_mask(ngtcp2_pkt_hd *dest, const uint8_t *pkt, + size_t pktlen, size_t dcidlen); + +/* + * path_init initializes |path| with the given arguments. They form + * IPv4 addresses. + */ +void path_init(ngtcp2_path_storage *path, uint32_t local_addr, + uint16_t local_port, uint32_t remote_addr, uint16_t remote_port); + +#endif /* NGTCP2_TEST_HELPER_H */ diff --git a/tests/ngtcp2_vec_test.c b/tests/ngtcp2_vec_test.c new file mode 100644 index 0000000..16cfba4 --- /dev/null +++ b/tests/ngtcp2_vec_test.c @@ -0,0 +1,426 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "ngtcp2_vec_test.h" + +#include <CUnit/CUnit.h> + +#include "ngtcp2_vec.h" +#include "ngtcp2_test_helper.h" + +void test_ngtcp2_vec_split(void) { + uint8_t nulldata[1024]; + ngtcp2_vec a[16], b[16]; + size_t acnt, bcnt; + ngtcp2_ssize nsplit; + + /* No split occurs */ + acnt = 1; + a[0].len = 135; + a[0].base = nulldata; + + bcnt = 0; + b[0].len = 0; + b[0].base = NULL; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 135, 16); + + CU_ASSERT(0 == nsplit); + CU_ASSERT(1 == acnt); + CU_ASSERT(135 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(0 == bcnt); + CU_ASSERT(0 == b[0].len); + CU_ASSERT(NULL == b[0].base); + + /* Split once */ + acnt = 1; + a[0].len = 135; + a[0].base = nulldata; + + bcnt = 0; + b[0].len = 0; + b[0].base = NULL; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 87, 16); + + CU_ASSERT(48 == nsplit); + CU_ASSERT(1 == acnt); + CU_ASSERT(87 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(1 == bcnt); + CU_ASSERT(48 == b[0].len); + CU_ASSERT(nulldata + 87 == b[0].base); + + /* Multiple a vector; split at ngtcp2_vec boundary */ + acnt = 2; + a[0].len = 33; + a[0].base = nulldata; + a[1].len = 89; + a[1].base = nulldata + 33; + + bcnt = 0; + b[0].len = 0; + b[0].base = NULL; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 33, 16); + + CU_ASSERT(89 == nsplit); + CU_ASSERT(1 == acnt); + CU_ASSERT(33 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(1 == bcnt); + CU_ASSERT(89 == b[0].len); + CU_ASSERT(nulldata + 33 == b[0].base); + + /* Multiple a vector; not split at ngtcp2_vec boundary */ + acnt = 3; + a[0].len = 33; + a[0].base = nulldata; + a[1].len = 89; + a[1].base = nulldata + 33; + a[2].len = 211; + a[2].base = nulldata + 33 + 89; + + bcnt = 0; + b[0].len = 0; + b[0].base = NULL; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 34, 16); + + CU_ASSERT(88 + 211 == nsplit); + CU_ASSERT(2 == acnt); + CU_ASSERT(33 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(1 == a[1].len); + CU_ASSERT(nulldata + 33 == a[1].base); + CU_ASSERT(2 == bcnt); + CU_ASSERT(88 == b[0].len); + CU_ASSERT(nulldata + 34 == b[0].base); + CU_ASSERT(211 == b[1].len); + CU_ASSERT(nulldata + 34 + 88 == b[1].base); + + /* Multiple a vector; split at ngtcp2_vec boundary; continuous + data */ + acnt = 2; + a[0].len = 33; + a[0].base = nulldata; + a[1].len = 89; + a[1].base = nulldata + 33; + + bcnt = 2; + b[0].len = 17; + b[0].base = nulldata + 33 + 89; + b[1].len = 3; + b[1].base = nulldata + 33 + 89 + 17; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 33, 16); + + CU_ASSERT(89 == nsplit); + CU_ASSERT(1 == acnt); + CU_ASSERT(33 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(2 == bcnt); + CU_ASSERT(89 + 17 == b[0].len); + CU_ASSERT(nulldata + 33 == b[0].base); + CU_ASSERT(3 == b[1].len); + CU_ASSERT(nulldata + 33 + 89 + 17 == b[1].base); + + /* Multiple a vector; not split at ngtcp2_vec boundary; continuous + data; nmove == 0 */ + acnt = 2; + a[0].len = 33; + a[0].base = nulldata; + a[1].len = 89; + a[1].base = nulldata + 33; + + bcnt = 2; + b[0].len = 17; + b[0].base = nulldata + 33 + 89; + b[1].len = 3; + b[1].base = nulldata + 33 + 89 + 17; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 34, 16); + + CU_ASSERT(88 == nsplit); + CU_ASSERT(2 == acnt); + CU_ASSERT(33 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(1 == a[1].len); + CU_ASSERT(nulldata + 33 == a[1].base); + CU_ASSERT(2 == bcnt); + CU_ASSERT(88 + 17 == b[0].len); + CU_ASSERT(nulldata + 34 == b[0].base); + CU_ASSERT(3 == b[1].len); + CU_ASSERT(nulldata + 33 + 89 + 17 == b[1].base); + + /* Multiple a vector; not split at ngtcp2_vec boundary; continuous + data */ + acnt = 3; + a[0].len = 33; + a[0].base = nulldata; + a[1].len = 89; + a[1].base = nulldata + 33; + a[2].len = 211; + a[2].base = nulldata + 33 + 89; + + bcnt = 2; + b[0].len = 17; + b[0].base = nulldata + 33 + 89 + 211; + b[1].len = 3; + b[1].base = nulldata + 33 + 89 + 211 + 17; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 34, 16); + + CU_ASSERT(88 + 211 == nsplit); + CU_ASSERT(2 == acnt); + CU_ASSERT(33 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(1 == a[1].len); + CU_ASSERT(nulldata + 33 == a[1].base); + CU_ASSERT(3 == bcnt); + CU_ASSERT(88 == b[0].len); + CU_ASSERT(nulldata + 34 == b[0].base); + CU_ASSERT(211 + 17 == b[1].len); + CU_ASSERT(nulldata + 34 + 88 == b[1].base); + CU_ASSERT(3 == b[2].len); + CU_ASSERT(nulldata + 33 + 89 + 211 + 17 == b[2].base); + + /* Multiple a vector; split at ngtcp2_vec boundary; not continuous + data */ + acnt = 2; + a[0].len = 33; + a[0].base = nulldata; + a[1].len = 89; + a[1].base = nulldata + 33; + + bcnt = 2; + b[0].len = 17; + b[0].base = nulldata + 256; + b[1].len = 3; + b[1].base = nulldata + 256 + 17; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 33, 16); + + CU_ASSERT(89 == nsplit); + CU_ASSERT(1 == acnt); + CU_ASSERT(33 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(3 == bcnt); + CU_ASSERT(89 == b[0].len); + CU_ASSERT(nulldata + 33 == b[0].base); + CU_ASSERT(17 == b[1].len); + CU_ASSERT(nulldata + 256 == b[1].base); + CU_ASSERT(3 == b[2].len); + CU_ASSERT(nulldata + 256 + 17 == b[2].base); + + /* maxcnt exceeded; continuous */ + acnt = 2; + a[0].len = 33; + a[0].base = nulldata; + a[1].len = 89; + a[1].base = nulldata + 33; + + bcnt = 1; + b[0].len = 17; + b[0].base = nulldata + 33 + 89; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 32, 1); + + CU_ASSERT(-1 == nsplit); + + /* maxcnt exceeded; not continuous */ + acnt = 2; + a[0].len = 33; + a[0].base = nulldata; + a[1].len = 89; + a[1].base = nulldata + 33; + + bcnt = 1; + b[0].len = 17; + b[0].base = nulldata + 256; + + nsplit = ngtcp2_vec_split(a, &acnt, b, &bcnt, 33, 1); + + CU_ASSERT(-1 == nsplit); +} + +void test_ngtcp2_vec_merge(void) { + uint8_t nulldata[1024]; + ngtcp2_vec a[16], b[16]; + size_t acnt, bcnt; + size_t nmerged; + + /* Merge one ngtcp2_vec completely */ + acnt = 1; + a[0].len = 33; + a[0].base = nulldata; + + bcnt = 1; + b[0].len = 11; + b[0].base = nulldata + 33; + + nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 11, 16); + + CU_ASSERT(11 == nmerged); + CU_ASSERT(1 == acnt); + CU_ASSERT(44 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(0 == bcnt); + + /* Merge ngtcp2_vec partially */ + acnt = 1; + a[0].len = 33; + a[0].base = nulldata; + + bcnt = 1; + b[0].len = 11; + b[0].base = nulldata + 33; + + nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 10, 16); + + CU_ASSERT(10 == nmerged); + CU_ASSERT(1 == acnt); + CU_ASSERT(43 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(1 == bcnt); + CU_ASSERT(1 == b[0].len); + CU_ASSERT(nulldata + 33 + 10 == b[0].base); + + /* Merge one ngtcp2_vec completely; data is not continuous */ + acnt = 1; + a[0].len = 33; + a[0].base = nulldata; + + bcnt = 1; + b[0].len = 11; + b[0].base = nulldata + 256; + + nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 11, 16); + + CU_ASSERT(11 == nmerged); + CU_ASSERT(2 == acnt); + CU_ASSERT(33 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(11 == a[1].len); + CU_ASSERT(nulldata + 256 == a[1].base); + CU_ASSERT(0 == bcnt); + + /* Merge ngtcp2_vec partially; data is not continuous */ + acnt = 1; + a[0].len = 33; + a[0].base = nulldata; + + bcnt = 1; + b[0].len = 11; + b[0].base = nulldata + 256; + + nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 10, 16); + + CU_ASSERT(10 == nmerged); + CU_ASSERT(2 == acnt); + CU_ASSERT(33 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(10 == a[1].len); + CU_ASSERT(nulldata + 256 == a[1].base); + CU_ASSERT(1 == bcnt); + CU_ASSERT(1 == b[0].len); + CU_ASSERT(nulldata + 256 + 10 == b[0].base); + + /* Merge ends at the ngtcp2_vec boundary */ + acnt = 1; + a[0].len = 33; + a[0].base = nulldata; + + bcnt = 2; + b[0].len = 11; + b[0].base = nulldata + 256; + b[1].len = 19; + b[1].base = nulldata + 256 + 11; + + nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 11, 16); + + CU_ASSERT(11 == nmerged); + CU_ASSERT(2 == acnt); + CU_ASSERT(33 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(11 == a[1].len); + CU_ASSERT(nulldata + 256 == a[1].base); + CU_ASSERT(1 == bcnt); + CU_ASSERT(19 == b[0].len); + CU_ASSERT(nulldata + 256 + 11 == b[0].base); + + /* Merge occurs at the last object */ + acnt = 1; + a[0].len = 33; + a[0].base = nulldata; + + bcnt = 2; + b[0].len = 11; + b[0].base = nulldata + 33; + b[1].len = 99; + b[1].base = nulldata + 33 + 11; + + nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 100, 1); + + CU_ASSERT(100 == nmerged); + CU_ASSERT(1 == acnt); + CU_ASSERT(133 == a[0].len); + CU_ASSERT(nulldata == a[0].base); + CU_ASSERT(1 == bcnt); + CU_ASSERT(10 == b[0].len); + CU_ASSERT(nulldata + 33 + 11 + 89 == b[0].base); + + /* No merge occurs if object is full */ + acnt = 1; + a[0].len = 33; + a[0].base = nulldata; + + bcnt = 1; + b[0].len = 3; + b[0].base = nulldata + 100; + + nmerged = ngtcp2_vec_merge(a, &acnt, b, &bcnt, 3, 1); + + CU_ASSERT(0 == nmerged); +} + +void test_ngtcp2_vec_len_varint(void) { + CU_ASSERT(0 == ngtcp2_vec_len_varint(NULL, 0)); + +#if SIZE_MAX == UINT64_MAX + { + ngtcp2_vec v[] = {{NULL, NGTCP2_MAX_VARINT}, {NULL, 1}}; + + CU_ASSERT(-1 == ngtcp2_vec_len_varint(v, ngtcp2_arraylen(v))); + } + + { + ngtcp2_vec v[] = {{NULL, NGTCP2_MAX_VARINT - 1}, {NULL, 1}}; + + CU_ASSERT(NGTCP2_MAX_VARINT == + ngtcp2_vec_len_varint(v, ngtcp2_arraylen(v))); + } +#endif /* SIZE_MAX == UINT64_MAX */ +} diff --git a/tests/ngtcp2_vec_test.h b/tests/ngtcp2_vec_test.h new file mode 100644 index 0000000..56e5d54 --- /dev/null +++ b/tests/ngtcp2_vec_test.h @@ -0,0 +1,36 @@ +/* + * ngtcp2 + * + * Copyright (c) 2018 ngtcp2 contributors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef NGTCP2_VEC_TEST_H +#define NGTCP2_VEC_TEST_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +void test_ngtcp2_vec_split(void); +void test_ngtcp2_vec_merge(void); +void test_ngtcp2_vec_len_varint(void); + +#endif /* NGTCP2_VEC_TEST_H */ diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt new file mode 100644 index 0000000..b8a8923 --- /dev/null +++ b/third-party/CMakeLists.txt @@ -0,0 +1,34 @@ +# ngtcp2 + +# Copyright (c) 2017 ngtcp2 contributors +# Copyright (c) 2014 nghttp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if(LIBEV_FOUND AND LIBNGHTTP3_FOUND) + +set(LIBHTTP_PARSER_SOURCES + http-parser/http_parser.c +) +add_library(http-parser OBJECT ${LIBHTTP_PARSER_SOURCES}) +set_target_properties(http-parser PROPERTIES + POSITION_INDEPENDENT_CODE ON) + +endif() diff --git a/third-party/Makefile.am b/third-party/Makefile.am new file mode 100644 index 0000000..55c21e4 --- /dev/null +++ b/third-party/Makefile.am @@ -0,0 +1,31 @@ +# ngtcp2 + +# Copyright (c) 2017 ngtcp2 contributors +# Copyright (c) 2014 nghttp2 contributors + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +EXTRA_DIST = CMakeLists.txt + +AM_CPPFLAGS = @DEFS@ + +noinst_LTLIBRARIES = libhttp-parser.la +libhttp_parser_la_SOURCES = \ + http-parser/http_parser.c \ + http-parser/http_parser.h diff --git a/third-party/http-parser/.gitignore b/third-party/http-parser/.gitignore new file mode 100644 index 0000000..c122e76 --- /dev/null +++ b/third-party/http-parser/.gitignore @@ -0,0 +1,30 @@ +/out/ +core +tags +*.o +test +test_g +test_fast +bench +url_parser +parsertrace +parsertrace_g +*.mk +*.Makefile +*.so.* +*.exe.* +*.exe +*.a + + +# Visual Studio uglies +*.suo +*.sln +*.vcxproj +*.vcxproj.filters +*.vcxproj.user +*.opensdf +*.ncrunchsolution* +*.sdf +*.vsp +*.psess diff --git a/third-party/http-parser/.mailmap b/third-party/http-parser/.mailmap new file mode 100644 index 0000000..278d141 --- /dev/null +++ b/third-party/http-parser/.mailmap @@ -0,0 +1,8 @@ +# update AUTHORS with: +# git log --all --reverse --format='%aN <%aE>' | perl -ne 'BEGIN{print "# Authors ordered by first contribution.\n"} print unless $h{$_}; $h{$_} = 1' > AUTHORS +Ryan Dahl <ry@tinyclouds.org> +Salman Haq <salman.haq@asti-usa.com> +Simon Zimmermann <simonz05@gmail.com> +Thomas LE ROUX <thomas@november-eleven.fr> LE ROUX Thomas <thomas@procheo.fr> +Thomas LE ROUX <thomas@november-eleven.fr> Thomas LE ROUX <thomas@procheo.fr> +Fedor Indutny <fedor@indutny.com> diff --git a/third-party/http-parser/.travis.yml b/third-party/http-parser/.travis.yml new file mode 100644 index 0000000..4b038e6 --- /dev/null +++ b/third-party/http-parser/.travis.yml @@ -0,0 +1,13 @@ +language: c + +compiler: + - clang + - gcc + +script: + - "make" + +notifications: + email: false + irc: + - "irc.freenode.net#node-ci" diff --git a/third-party/http-parser/AUTHORS b/third-party/http-parser/AUTHORS new file mode 100644 index 0000000..5323b68 --- /dev/null +++ b/third-party/http-parser/AUTHORS @@ -0,0 +1,68 @@ +# Authors ordered by first contribution. +Ryan Dahl <ry@tinyclouds.org> +Jeremy Hinegardner <jeremy@hinegardner.org> +Sergey Shepelev <temotor@gmail.com> +Joe Damato <ice799@gmail.com> +tomika <tomika_nospam@freemail.hu> +Phoenix Sol <phoenix@burninglabs.com> +Cliff Frey <cliff@meraki.com> +Ewen Cheslack-Postava <ewencp@cs.stanford.edu> +Santiago Gala <sgala@apache.org> +Tim Becker <tim.becker@syngenio.de> +Jeff Terrace <jterrace@gmail.com> +Ben Noordhuis <info@bnoordhuis.nl> +Nathan Rajlich <nathan@tootallnate.net> +Mark Nottingham <mnot@mnot.net> +Aman Gupta <aman@tmm1.net> +Tim Becker <tim.becker@kuriositaet.de> +Sean Cunningham <sean.cunningham@mandiant.com> +Peter Griess <pg@std.in> +Salman Haq <salman.haq@asti-usa.com> +Cliff Frey <clifffrey@gmail.com> +Jon Kolb <jon@b0g.us> +Fouad Mardini <f.mardini@gmail.com> +Paul Querna <pquerna@apache.org> +Felix Geisendörfer <felix@debuggable.com> +koichik <koichik@improvement.jp> +Andre Caron <andre.l.caron@gmail.com> +Ivo Raisr <ivosh@ivosh.net> +James McLaughlin <jamie@lacewing-project.org> +David Gwynne <loki@animata.net> +Thomas LE ROUX <thomas@november-eleven.fr> +Randy Rizun <rrizun@ortivawireless.com> +Andre Louis Caron <andre.louis.caron@usherbrooke.ca> +Simon Zimmermann <simonz05@gmail.com> +Erik Dubbelboer <erik@dubbelboer.com> +Martell Malone <martellmalone@gmail.com> +Bertrand Paquet <bpaquet@octo.com> +BogDan Vatra <bogdan@kde.org> +Peter Faiman <peter@thepicard.org> +Corey Richardson <corey@octayn.net> +Tóth Tamás <tomika_nospam@freemail.hu> +Cam Swords <cam.swords@gmail.com> +Chris Dickinson <christopher.s.dickinson@gmail.com> +Uli Köhler <ukoehler@btronik.de> +Charlie Somerville <charlie@charliesomerville.com> +Patrik Stutz <patrik.stutz@gmail.com> +Fedor Indutny <fedor.indutny@gmail.com> +runner <runner.mei@gmail.com> +Alexis Campailla <alexis@janeasystems.com> +David Wragg <david@wragg.org> +Vinnie Falco <vinnie.falco@gmail.com> +Alex Butum <alexbutum@linux.com> +Rex Feng <rexfeng@gmail.com> +Alex Kocharin <alex@kocharin.ru> +Mark Koopman <markmontymark@yahoo.com> +Helge Heß <me@helgehess.eu> +Alexis La Goutte <alexis.lagoutte@gmail.com> +George Miroshnykov <george.miroshnykov@gmail.com> +Maciej MaÅ‚ecki <me@mmalecki.com> +Marc O'Morain <github.com@marcomorain.com> +Jeff Pinner <jpinner@twitter.com> +Timothy J Fontaine <tjfontaine@gmail.com> +Akagi201 <akagi201@gmail.com> +Romain Giraud <giraud.romain@gmail.com> +Jay Satiro <raysatiro@yahoo.com> +Arne Steen <Arne.Steen@gmx.de> +Kjell Schubert <kjell.schubert@gmail.com> +Olivier Mengué <dolmen@cpan.org> diff --git a/third-party/http-parser/LICENSE-MIT b/third-party/http-parser/LICENSE-MIT new file mode 100644 index 0000000..58010b3 --- /dev/null +++ b/third-party/http-parser/LICENSE-MIT @@ -0,0 +1,23 @@ +http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright +Igor Sysoev. + +Additional changes are licensed under the same terms as NGINX and +copyright Joyent, Inc. and other Node contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/third-party/http-parser/Makefile b/third-party/http-parser/Makefile new file mode 100644 index 0000000..04ec3d3 --- /dev/null +++ b/third-party/http-parser/Makefile @@ -0,0 +1,157 @@ +# Copyright Joyent, Inc. and other Node contributors. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +PLATFORM ?= $(shell sh -c 'uname -s | tr "[A-Z]" "[a-z]"') +HELPER ?= +BINEXT ?= +SOLIBNAME = libhttp_parser +SOMAJOR = 2 +SOMINOR = 7 +SOREV = 1 +ifeq (darwin,$(PLATFORM)) +SOEXT ?= dylib +SONAME ?= $(SOLIBNAME).$(SOMAJOR).$(SOMINOR).$(SOEXT) +LIBNAME ?= $(SOLIBNAME).$(SOMAJOR).$(SOMINOR).$(SOREV).$(SOEXT) +else ifeq (wine,$(PLATFORM)) +CC = winegcc +BINEXT = .exe.so +HELPER = wine +else +SOEXT ?= so +SONAME ?= $(SOLIBNAME).$(SOEXT).$(SOMAJOR).$(SOMINOR) +LIBNAME ?= $(SOLIBNAME).$(SOEXT).$(SOMAJOR).$(SOMINOR).$(SOREV) +endif + +CC?=gcc +AR?=ar + +CPPFLAGS ?= +LDFLAGS ?= + +CPPFLAGS += -I. +CPPFLAGS_DEBUG = $(CPPFLAGS) -DHTTP_PARSER_STRICT=1 +CPPFLAGS_DEBUG += $(CPPFLAGS_DEBUG_EXTRA) +CPPFLAGS_FAST = $(CPPFLAGS) -DHTTP_PARSER_STRICT=0 +CPPFLAGS_FAST += $(CPPFLAGS_FAST_EXTRA) +CPPFLAGS_BENCH = $(CPPFLAGS_FAST) + +CFLAGS += -Wall -Wextra -Werror +CFLAGS_DEBUG = $(CFLAGS) -O0 -g $(CFLAGS_DEBUG_EXTRA) +CFLAGS_FAST = $(CFLAGS) -O3 $(CFLAGS_FAST_EXTRA) +CFLAGS_BENCH = $(CFLAGS_FAST) -Wno-unused-parameter +CFLAGS_LIB = $(CFLAGS_FAST) -fPIC + +LDFLAGS_LIB = $(LDFLAGS) -shared + +INSTALL ?= install +PREFIX ?= $(DESTDIR)/usr/local +LIBDIR = $(PREFIX)/lib +INCLUDEDIR = $(PREFIX)/include + +ifneq (darwin,$(PLATFORM)) +# TODO(bnoordhuis) The native SunOS linker expects -h rather than -soname... +LDFLAGS_LIB += -Wl,-soname=$(SONAME) +endif + +test: test_g test_fast + $(HELPER) ./test_g$(BINEXT) + $(HELPER) ./test_fast$(BINEXT) + +test_g: http_parser_g.o test_g.o + $(CC) $(CFLAGS_DEBUG) $(LDFLAGS) http_parser_g.o test_g.o -o $@ + +test_g.o: test.c http_parser.h Makefile + $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) -c test.c -o $@ + +http_parser_g.o: http_parser.c http_parser.h Makefile + $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) -c http_parser.c -o $@ + +test_fast: http_parser.o test.o http_parser.h + $(CC) $(CFLAGS_FAST) $(LDFLAGS) http_parser.o test.o -o $@ + +test.o: test.c http_parser.h Makefile + $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) -c test.c -o $@ + +bench: http_parser.o bench.o + $(CC) $(CFLAGS_BENCH) $(LDFLAGS) http_parser.o bench.o -o $@ + +bench.o: bench.c http_parser.h Makefile + $(CC) $(CPPFLAGS_BENCH) $(CFLAGS_BENCH) -c bench.c -o $@ + +http_parser.o: http_parser.c http_parser.h Makefile + $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) -c http_parser.c + +test-run-timed: test_fast + while(true) do time $(HELPER) ./test_fast$(BINEXT) > /dev/null; done + +test-valgrind: test_g + valgrind ./test_g + +libhttp_parser.o: http_parser.c http_parser.h Makefile + $(CC) $(CPPFLAGS_FAST) $(CFLAGS_LIB) -c http_parser.c -o libhttp_parser.o + +library: libhttp_parser.o + $(CC) $(LDFLAGS_LIB) -o $(LIBNAME) $< + +package: http_parser.o + $(AR) rcs libhttp_parser.a http_parser.o + +url_parser: http_parser.o contrib/url_parser.c + $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) $^ -o $@ + +url_parser_g: http_parser_g.o contrib/url_parser.c + $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) $^ -o $@ + +parsertrace: http_parser.o contrib/parsertrace.c + $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) $^ -o parsertrace$(BINEXT) + +parsertrace_g: http_parser_g.o contrib/parsertrace.c + $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) $^ -o parsertrace_g$(BINEXT) + +tags: http_parser.c http_parser.h test.c + ctags $^ + +install: library + $(INSTALL) -D http_parser.h $(INCLUDEDIR)/http_parser.h + $(INSTALL) -D $(LIBNAME) $(LIBDIR)/$(LIBNAME) + ln -s $(LIBDIR)/$(LIBNAME) $(LIBDIR)/$(SONAME) + ln -s $(LIBDIR)/$(LIBNAME) $(LIBDIR)/$(SOLIBNAME).$(SOEXT) + +install-strip: library + $(INSTALL) -D http_parser.h $(INCLUDEDIR)/http_parser.h + $(INSTALL) -D -s $(LIBNAME) $(LIBDIR)/$(LIBNAME) + ln -s $(LIBDIR)/$(LIBNAME) $(LIBDIR)/$(SONAME) + ln -s $(LIBDIR)/$(LIBNAME) $(LIBDIR)/$(SOLIBNAME).$(SOEXT) + +uninstall: + rm $(INCLUDEDIR)/http_parser.h + rm $(LIBDIR)/$(SONAME) + rm $(LIBDIR)/libhttp_parser.so + +clean: + rm -f *.o *.a tags test test_fast test_g \ + http_parser.tar libhttp_parser.so.* \ + url_parser url_parser_g parsertrace parsertrace_g \ + *.exe *.exe.so + +contrib/url_parser.c: http_parser.h +contrib/parsertrace.c: http_parser.h + +.PHONY: clean package test-run test-run-timed test-valgrind install install-strip uninstall diff --git a/third-party/http-parser/README.md b/third-party/http-parser/README.md new file mode 100644 index 0000000..439b309 --- /dev/null +++ b/third-party/http-parser/README.md @@ -0,0 +1,246 @@ +HTTP Parser +=========== + +[![Build Status](https://api.travis-ci.org/nodejs/http-parser.svg?branch=master)](https://travis-ci.org/nodejs/http-parser) + +This is a parser for HTTP messages written in C. It parses both requests and +responses. The parser is designed to be used in performance HTTP +applications. It does not make any syscalls nor allocations, it does not +buffer data, it can be interrupted at anytime. Depending on your +architecture, it only requires about 40 bytes of data per message +stream (in a web server that is per connection). + +Features: + + * No dependencies + * Handles persistent streams (keep-alive). + * Decodes chunked encoding. + * Upgrade support + * Defends against buffer overflow attacks. + +The parser extracts the following information from HTTP messages: + + * Header fields and values + * Content-Length + * Request method + * Response status code + * Transfer-Encoding + * HTTP version + * Request URL + * Message body + + +Usage +----- + +One `http_parser` object is used per TCP connection. Initialize the struct +using `http_parser_init()` and set the callbacks. That might look something +like this for a request parser: +```c +http_parser_settings settings; +settings.on_url = my_url_callback; +settings.on_header_field = my_header_field_callback; +/* ... */ + +http_parser *parser = malloc(sizeof(http_parser)); +http_parser_init(parser, HTTP_REQUEST); +parser->data = my_socket; +``` + +When data is received on the socket execute the parser and check for errors. + +```c +size_t len = 80*1024, nparsed; +char buf[len]; +ssize_t recved; + +recved = recv(fd, buf, len, 0); + +if (recved < 0) { + /* Handle error. */ +} + +/* Start up / continue the parser. + * Note we pass recved==0 to signal that EOF has been received. + */ +nparsed = http_parser_execute(parser, &settings, buf, recved); + +if (parser->upgrade) { + /* handle new protocol */ +} else if (nparsed != recved) { + /* Handle error. Usually just close the connection. */ +} +``` + +HTTP needs to know where the end of the stream is. For example, sometimes +servers send responses without Content-Length and expect the client to +consume input (for the body) until EOF. To tell http_parser about EOF, give +`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors +can still be encountered during an EOF, so one must still be prepared +to receive them. + +Scalar valued message information such as `status_code`, `method`, and the +HTTP version are stored in the parser structure. This data is only +temporally stored in `http_parser` and gets reset on each new message. If +this information is needed later, copy it out of the structure during the +`headers_complete` callback. + +The parser decodes the transfer-encoding for both requests and responses +transparently. That is, a chunked encoding is decoded before being sent to +the on_body callback. + + +The Special Problem of Upgrade +------------------------------ + +HTTP supports upgrading the connection to a different protocol. An +increasingly common example of this is the WebSocket protocol which sends +a request like + + GET /demo HTTP/1.1 + Upgrade: WebSocket + Connection: Upgrade + Host: example.com + Origin: http://example.com + WebSocket-Protocol: sample + +followed by non-HTTP data. + +(See [RFC6455](https://tools.ietf.org/html/rfc6455) for more information the +WebSocket protocol.) + +To support this, the parser will treat this as a normal HTTP message without a +body, issuing both on_headers_complete and on_message_complete callbacks. However +http_parser_execute() will stop parsing at the end of the headers and return. + +The user is expected to check if `parser->upgrade` has been set to 1 after +`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied +offset by the return value of `http_parser_execute()`. + + +Callbacks +--------- + +During the `http_parser_execute()` call, the callbacks set in +`http_parser_settings` will be executed. The parser maintains state and +never looks behind, so buffering the data is not necessary. If you need to +save certain data for later usage, you can do that from the callbacks. + +There are two types of callbacks: + +* notification `typedef int (*http_cb) (http_parser*);` + Callbacks: on_message_begin, on_headers_complete, on_message_complete. +* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` + Callbacks: (requests only) on_url, + (common) on_header_field, on_header_value, on_body; + +Callbacks must return 0 on success. Returning a non-zero value indicates +error to the parser, making it exit immediately. + +For cases where it is necessary to pass local information to/from a callback, +the `http_parser` object's `data` field can be used. +An example of such a case is when using threads to handle a socket connection, +parse a request, and then give a response over that socket. By instantiation +of a thread-local struct containing relevant data (e.g. accepted socket, +allocated memory for callbacks to write into, etc), a parser's callbacks are +able to communicate data between the scope of the thread and the scope of the +callback in a threadsafe manner. This allows http-parser to be used in +multi-threaded contexts. + +Example: +```c + typedef struct { + socket_t sock; + void* buffer; + int buf_len; + } custom_data_t; + + +int my_url_callback(http_parser* parser, const char *at, size_t length) { + /* access to thread local custom_data_t struct. + Use this access save parsed data for later use into thread local + buffer, or communicate over socket + */ + parser->data; + ... + return 0; +} + +... + +void http_parser_thread(socket_t sock) { + int nparsed = 0; + /* allocate memory for user data */ + custom_data_t *my_data = malloc(sizeof(custom_data_t)); + + /* some information for use by callbacks. + * achieves thread -> callback information flow */ + my_data->sock = sock; + + /* instantiate a thread-local parser */ + http_parser *parser = malloc(sizeof(http_parser)); + http_parser_init(parser, HTTP_REQUEST); /* initialise parser */ + /* this custom data reference is accessible through the reference to the + parser supplied to callback functions */ + parser->data = my_data; + + http_parser_settings settings; /* set up callbacks */ + settings.on_url = my_url_callback; + + /* execute parser */ + nparsed = http_parser_execute(parser, &settings, buf, recved); + + ... + /* parsed information copied from callback. + can now perform action on data copied into thread-local memory from callbacks. + achieves callback -> thread information flow */ + my_data->buffer; + ... +} + +``` + +In case you parse HTTP message in chunks (i.e. `read()` request line +from socket, parse, read half headers, parse, etc) your data callbacks +may be called more than once. Http-parser guarantees that data pointer is only +valid for the lifetime of callback. You can also `read()` into a heap allocated +buffer to avoid copying memory around if this fits your application. + +Reading headers may be a tricky task if you read/parse headers partially. +Basically, you need to remember whether last header callback was field or value +and apply the following logic: + + (on_header_field and on_header_value shortened to on_h_*) + ------------------------ ------------ -------------------------------------------- + | State (prev. callback) | Callback | Description/action | + ------------------------ ------------ -------------------------------------------- + | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | + | | | into it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_field | New header started. | + | | | Copy current name,value buffers to headers | + | | | list and allocate new buffer for new name | + ------------------------ ------------ -------------------------------------------- + | field | on_h_field | Previous name continues. Reallocate name | + | | | buffer and append callback data to it | + ------------------------ ------------ -------------------------------------------- + | field | on_h_value | Value for current header started. Allocate | + | | | new buffer and copy callback data to it | + ------------------------ ------------ -------------------------------------------- + | value | on_h_value | Value continues. Reallocate value buffer | + | | | and append callback data to it | + ------------------------ ------------ -------------------------------------------- + + +Parsing URLs +------------ + +A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`. +Users of this library may wish to use it to parse URLs constructed from +consecutive `on_url` callbacks. + +See examples of reading in headers: + +* [partial example](http://gist.github.com/155877) in C +* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C +* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript diff --git a/third-party/http-parser/bench.c b/third-party/http-parser/bench.c new file mode 100644 index 0000000..5b452fa --- /dev/null +++ b/third-party/http-parser/bench.c @@ -0,0 +1,111 @@ +/* Copyright Fedor Indutny. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <sys/time.h> + +static const char data[] = + "POST /joyent/http-parser HTTP/1.1\r\n" + "Host: github.com\r\n" + "DNT: 1\r\n" + "Accept-Encoding: gzip, deflate, sdch\r\n" + "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n" + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/39.0.2171.65 Safari/537.36\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9," + "image/webp,*/*;q=0.8\r\n" + "Referer: https://github.com/joyent/http-parser\r\n" + "Connection: keep-alive\r\n" + "Transfer-Encoding: chunked\r\n" + "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n"; +static const size_t data_len = sizeof(data) - 1; + +static int on_info(http_parser* p) { + return 0; +} + + +static int on_data(http_parser* p, const char *at, size_t length) { + return 0; +} + +static http_parser_settings settings = { + .on_message_begin = on_info, + .on_headers_complete = on_info, + .on_message_complete = on_info, + .on_header_field = on_data, + .on_header_value = on_data, + .on_url = on_data, + .on_status = on_data, + .on_body = on_data +}; + +int bench(int iter_count, int silent) { + struct http_parser parser; + int i; + int err; + struct timeval start; + struct timeval end; + float rps; + + if (!silent) { + err = gettimeofday(&start, NULL); + assert(err == 0); + } + + for (i = 0; i < iter_count; i++) { + size_t parsed; + http_parser_init(&parser, HTTP_REQUEST); + + parsed = http_parser_execute(&parser, &settings, data, data_len); + assert(parsed == data_len); + } + + if (!silent) { + err = gettimeofday(&end, NULL); + assert(err == 0); + + fprintf(stdout, "Benchmark result:\n"); + + rps = (float) (end.tv_sec - start.tv_sec) + + (end.tv_usec - start.tv_usec) * 1e-6f; + fprintf(stdout, "Took %f seconds to run\n", rps); + + rps = (float) iter_count / rps; + fprintf(stdout, "%f req/sec\n", rps); + fflush(stdout); + } + + return 0; +} + +int main(int argc, char** argv) { + if (argc == 2 && strcmp(argv[1], "infinite") == 0) { + for (;;) + bench(5000000, 1); + return 0; + } else { + return bench(5000000, 0); + } +} diff --git a/third-party/http-parser/contrib/parsertrace.c b/third-party/http-parser/contrib/parsertrace.c new file mode 100644 index 0000000..e715368 --- /dev/null +++ b/third-party/http-parser/contrib/parsertrace.c @@ -0,0 +1,160 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* Dump what the parser finds to stdout as it happen */ + +#include "http_parser.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int on_message_begin(http_parser* _) { + (void)_; + printf("\n***MESSAGE BEGIN***\n\n"); + return 0; +} + +int on_headers_complete(http_parser* _) { + (void)_; + printf("\n***HEADERS COMPLETE***\n\n"); + return 0; +} + +int on_message_complete(http_parser* _) { + (void)_; + printf("\n***MESSAGE COMPLETE***\n\n"); + return 0; +} + +int on_url(http_parser* _, const char* at, size_t length) { + (void)_; + printf("Url: %.*s\n", (int)length, at); + return 0; +} + +int on_header_field(http_parser* _, const char* at, size_t length) { + (void)_; + printf("Header field: %.*s\n", (int)length, at); + return 0; +} + +int on_header_value(http_parser* _, const char* at, size_t length) { + (void)_; + printf("Header value: %.*s\n", (int)length, at); + return 0; +} + +int on_body(http_parser* _, const char* at, size_t length) { + (void)_; + printf("Body: %.*s\n", (int)length, at); + return 0; +} + +void usage(const char* name) { + fprintf(stderr, + "Usage: %s $type $filename\n" + " type: -x, where x is one of {r,b,q}\n" + " parses file as a Response, reQuest, or Both\n", + name); + exit(EXIT_FAILURE); +} + +int main(int argc, char* argv[]) { + enum http_parser_type file_type; + + if (argc != 3) { + usage(argv[0]); + } + + char* type = argv[1]; + if (type[0] != '-') { + usage(argv[0]); + } + + switch (type[1]) { + /* in the case of "-", type[1] will be NUL */ + case 'r': + file_type = HTTP_RESPONSE; + break; + case 'q': + file_type = HTTP_REQUEST; + break; + case 'b': + file_type = HTTP_BOTH; + break; + default: + usage(argv[0]); + } + + char* filename = argv[2]; + FILE* file = fopen(filename, "r"); + if (file == NULL) { + perror("fopen"); + goto fail; + } + + fseek(file, 0, SEEK_END); + long file_length = ftell(file); + if (file_length == -1) { + perror("ftell"); + goto fail; + } + fseek(file, 0, SEEK_SET); + + char* data = malloc(file_length); + if (fread(data, 1, file_length, file) != (size_t)file_length) { + fprintf(stderr, "couldn't read entire file\n"); + free(data); + goto fail; + } + + http_parser_settings settings; + memset(&settings, 0, sizeof(settings)); + settings.on_message_begin = on_message_begin; + settings.on_url = on_url; + settings.on_header_field = on_header_field; + settings.on_header_value = on_header_value; + settings.on_headers_complete = on_headers_complete; + settings.on_body = on_body; + settings.on_message_complete = on_message_complete; + + http_parser parser; + http_parser_init(&parser, file_type); + size_t nparsed = http_parser_execute(&parser, &settings, data, file_length); + free(data); + + if (nparsed != (size_t)file_length) { + fprintf(stderr, + "Error: %s (%s)\n", + http_errno_description(HTTP_PARSER_ERRNO(&parser)), + http_errno_name(HTTP_PARSER_ERRNO(&parser))); + goto fail; + } + + return EXIT_SUCCESS; + +fail: + fclose(file); + return EXIT_FAILURE; +} diff --git a/third-party/http-parser/contrib/url_parser.c b/third-party/http-parser/contrib/url_parser.c new file mode 100644 index 0000000..f235bed --- /dev/null +++ b/third-party/http-parser/contrib/url_parser.c @@ -0,0 +1,47 @@ +#include "http_parser.h" +#include <stdio.h> +#include <string.h> + +void +dump_url (const char *url, const struct http_parser_url *u) +{ + unsigned int i; + + printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port); + for (i = 0; i < UF_MAX; i++) { + if ((u->field_set & (1 << i)) == 0) { + printf("\tfield_data[%u]: unset\n", i); + continue; + } + + printf("\tfield_data[%u]: off: %u, len: %u, part: %.*s\n", + i, + u->field_data[i].off, + u->field_data[i].len, + u->field_data[i].len, + url + u->field_data[i].off); + } +} + +int main(int argc, char ** argv) { + struct http_parser_url u; + int len, connect, result; + + if (argc != 3) { + printf("Syntax : %s connect|get url\n", argv[0]); + return 1; + } + len = strlen(argv[2]); + connect = strcmp("connect", argv[1]) == 0 ? 1 : 0; + printf("Parsing %s, connect %d\n", argv[2], connect); + + http_parser_url_init(&u); + result = http_parser_parse_url(argv[2], len, connect, &u); + if (result != 0) { + printf("Parse error : %d\n", result); + return result; + } + printf("Parse ok, result : \n"); + dump_url(argv[2], &u); + return 0; +} diff --git a/third-party/http-parser/http_parser.c b/third-party/http-parser/http_parser.c new file mode 100644 index 0000000..0cc16cb --- /dev/null +++ b/third-party/http-parser/http_parser.c @@ -0,0 +1,2419 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include <assert.h> +#include <stddef.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#ifndef ELEM_AT +# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) +#endif + +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) + +#define CURRENT_STATE() p_state +#define UPDATE_STATE(V) p_state = (enum state) (V); +#define RETURN(V) \ +do { \ + parser->state = CURRENT_STATE(); \ + return (V); \ +} while (0); +#define REEXECUTE() \ + goto reexecute; \ + + +#ifdef __GNUC__ +# define LIKELY(X) __builtin_expect(!!(X), 1) +# define UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +# define LIKELY(X) (X) +# define UNLIKELY(X) (X) +#endif + + +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ + if (FOR##_mark) { \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != \ + settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ + SET_ERRNO(HPE_CB_##FOR); \ + } \ + UPDATE_STATE(parser->state); \ + \ + /* We either errored above or got paused; get out */ \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ + return (ER); \ + } \ + } \ + FOR##_mark = NULL; \ + } \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) + +/* Don't allow the total size of the HTTP headers (including the status + * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ +#define COUNT_HEADER_SIZE(V) \ +do { \ + parser->nread += (V); \ + if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ + SET_ERRNO(HPE_HEADER_OVERFLOW); \ + goto error; \ + } \ +} while (0) + + +#define PROXY_CONNECTION "proxy-connection" +#define CONNECTION "connection" +#define CONTENT_LENGTH "content-length" +#define TRANSFER_ENCODING "transfer-encoding" +#define UPGRADE "upgrade" +#define CHUNKED "chunked" +#define KEEP_ALIVE "keep-alive" +#define CLOSE "close" + + +static const char *method_strings[] = + { +#define XX(num, name, string) #string, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1*<any CHAR except CTLs or separators> + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +static const char tokens[256] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0, 0, 0, 0, 0, 0, 0, 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 0, '*', '+', 0, '-', '.', 0, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + '0', '1', '2', '3', '4', '5', '6', '7', +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + '8', '9', 0, 0, 0, 0, 0, 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 'x', 'y', 'z', 0, 0, 0, '^', '_', +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; + + +static const int8_t unhex[256] = + {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 + ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 + }; + + +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + + +static const uint8_t normal_url_char[32] = { +/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, +/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, +/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, +/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, +/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef T + +enum state + { s_dead = 1 /* important that this is > 0 */ + + , s_start_req_or_res + , s_res_or_resp_H + , s_start_res + , s_res_H + , s_res_HT + , s_res_HTT + , s_res_HTTP + , s_res_http_major + , s_res_http_dot + , s_res_http_minor + , s_res_http_end + , s_res_first_status_code + , s_res_status_code + , s_res_status_start + , s_res_status + , s_res_line_almost_done + + , s_start_req + + , s_req_method + , s_req_spaces_before_url + , s_req_schema + , s_req_schema_slash + , s_req_schema_slash_slash + , s_req_server_start + , s_req_server + , s_req_server_with_at + , s_req_path + , s_req_query_string_start + , s_req_query_string + , s_req_fragment_start + , s_req_fragment + , s_req_http_start + , s_req_http_H + , s_req_http_HT + , s_req_http_HTT + , s_req_http_HTTP + , s_req_http_major + , s_req_http_dot + , s_req_http_minor + , s_req_http_end + , s_req_line_almost_done + + , s_header_field_start + , s_header_field + , s_header_value_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + , s_chunk_data + , s_chunk_data_almost_done + , s_chunk_data_done + + , s_body_identity + , s_body_identity_eof + + , s_message_done + }; + + +#define PARSING_HEADER(state) (state <= s_headers_done) + + +enum header_states + { h_general = 0 + , h_C + , h_CO + , h_CON + + , h_matching_connection + , h_matching_proxy_connection + , h_matching_content_length + , h_matching_transfer_encoding + , h_matching_upgrade + + , h_connection + , h_content_length + , h_transfer_encoding + , h_upgrade + + , h_matching_transfer_encoding_chunked + , h_matching_connection_token_start + , h_matching_connection_keep_alive + , h_matching_connection_close + , h_matching_connection_upgrade + , h_matching_connection_token + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + , h_connection_upgrade + }; + +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_v6_zone_start + , s_http_host_v6_zone + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) + +#if HTTP_PARSER_STRICT +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) \ + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ + (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + +/** + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + **/ +#define IS_HEADER_CHAR(ch) \ + (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127)) + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + +int http_message_needs_eof(const http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; + const char *status_mark = 0; + enum state p_state = (enum state) parser->state; + const unsigned int lenient = parser->lenient_http_headers; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (CURRENT_STATE()) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (CURRENT_STATE() == s_header_field) + header_field_mark = data; + if (CURRENT_STATE() == s_header_value) + header_value_mark = data; + switch (CURRENT_STATE()) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + case s_res_status: + status_mark = data; + break; + default: + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(CURRENT_STATE())) + COUNT_HEADER_SIZE(1); + +reexecute: + switch (CURRENT_STATE()) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (LIKELY(ch == CR || ch == LF)) + break; + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + UPDATE_STATE(s_res_or_resp_H); + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + UPDATE_STATE(s_start_req); + REEXECUTE(); + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + UPDATE_STATE(s_res_HT); + } else { + if (UNLIKELY(ch != 'E')) { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + UPDATE_STATE(s_req_method); + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + UPDATE_STATE(s_res_H); + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HT); + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HTT); + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_res_HTTP); + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_res_http_major); + break; + + case s_res_http_major: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_res_http_dot); + break; + + case s_res_http_dot: + { + if (UNLIKELY(ch != '.')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + UPDATE_STATE(s_res_http_minor); + break; + } + + case s_res_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_res_http_end); + break; + + case s_res_http_end: + { + if (UNLIKELY(ch != ' ')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + UPDATE_STATE(s_res_first_status_code); + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + UPDATE_STATE(s_res_status_code); + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + UPDATE_STATE(s_res_status_start); + break; + case CR: + case LF: + UPDATE_STATE(s_res_status_start); + REEXECUTE(); + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (UNLIKELY(parser->status_code > 999)) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status_start: + { + MARK(status); + UPDATE_STATE(s_res_status); + parser->index = 0; + + if (ch == CR || ch == LF) + REEXECUTE(); + + break; + } + + case s_res_status: + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + CALLBACK_DATA(status); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA(status); + break; + } + + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_field_start); + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (UNLIKELY(!IS_ALPHA(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (enum http_method) 0; + parser->index = 1; + switch (ch) { + case 'A': parser->method = HTTP_ACL; break; + case 'B': parser->method = HTTP_BIND; break; + case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; + case 'D': parser->method = HTTP_DELETE; break; + case 'G': parser->method = HTTP_GET; break; + case 'H': parser->method = HTTP_HEAD; break; + case 'L': parser->method = HTTP_LOCK; /* or LINK */ break; + case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + UPDATE_STATE(s_req_method); + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (UNLIKELY(ch == '\0')) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + UPDATE_STATE(s_req_spaces_before_url); + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if ((ch >= 'A' && ch <= 'Z') || ch == '-') { + + switch (parser->method << 16 | parser->index << 8 | ch) { +#define XX(meth, pos, ch, new_meth) \ + case (HTTP_##meth << 16 | pos << 8 | ch): \ + parser->method = HTTP_##new_meth; break; + + XX(POST, 1, 'U', PUT) + XX(POST, 1, 'A', PATCH) + XX(POST, 1, 'R', PROPFIND) + XX(PUT, 2, 'R', PURGE) + XX(CONNECT, 1, 'H', CHECKOUT) + XX(CONNECT, 2, 'P', COPY) + XX(MKCOL, 1, 'O', MOVE) + XX(MKCOL, 1, 'E', MERGE) + XX(MKCOL, 1, '-', MSEARCH) + XX(MKCOL, 2, 'A', MKACTIVITY) + XX(MKCOL, 3, 'A', MKCALENDAR) + XX(SUBSCRIBE, 1, 'E', SEARCH) + XX(REPORT, 2, 'B', REBIND) + XX(PROPFIND, 4, 'P', PROPPATCH) + XX(LOCK, 1, 'I', LINK) + XX(UNLOCK, 2, 'S', UNSUBSCRIBE) + XX(UNLOCK, 2, 'B', UNBIND) + XX(UNLOCK, 3, 'I', UNLINK) +#undef XX + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { + UPDATE_STATE(s_req_server_start); + } + + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + { + switch (ch) { + case ' ': + UPDATE_STATE(s_req_http_start); + CALLBACK_DATA(url); + break; + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + CALLBACK_DATA(url); + if (ch == CR) { + UPDATE_STATE(s_req_line_almost_done); + } else { + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); + } + break; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + UPDATE_STATE(s_req_http_H); + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HT); + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HTT); + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_req_http_HTTP); + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_req_http_major); + break; + + case s_req_http_major: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_req_http_dot); + break; + + case s_req_http_dot: + { + if (UNLIKELY(ch != '.')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + UPDATE_STATE(s_req_http_minor); + break; + } + + case s_req_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_req_http_end); + break; + + case s_req_http_end: + { + if (ch == CR) { + UPDATE_STATE(s_req_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + if (parser->http_major == 0) { + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); + } else { + UPDATE_STATE(s_header_field_start); + } + break; + } + + case s_header_field_start: + { + if (ch == CR) { + UPDATE_STATE(s_headers_almost_done); + break; + } + + if (ch == LF) { + /* they might be just sending \n instead of \r\n so this would be + * the second \n to denote the end of headers*/ + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); + } + + c = TOKEN(ch); + + if (UNLIKELY(!c)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + UPDATE_STATE(s_header_field); + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + const char* start = p; + for (; p != data + len; p++) { + ch = *p; + c = TOKEN(ch); + + if (!c) + break; + + switch (parser->header_state) { + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + } + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) { + --p; + break; + } + + if (ch == ':') { + UPDATE_STATE(s_header_value_discard_ws); + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_discard_ws: + if (ch == ' ' || ch == '\t') break; + + if (ch == CR) { + UPDATE_STATE(s_header_value_discard_ws_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + /* FALLTHROUGH */ + + case s_header_value_start: + { + MARK(header_value); + + UPDATE_STATE(s_header_value); + parser->index = 0; + + c = LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_general; + } + break; + + case h_content_length: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + if (parser->flags & F_CONTENTLENGTH) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + + parser->flags |= F_CONTENTLENGTH; + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else if (c == 'u') { + parser->header_state = h_matching_connection_upgrade; + } else { + parser->header_state = h_matching_connection_token; + } + break; + + /* Multi-value `Connection` header */ + case h_matching_connection_token_start: + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + const char* start = p; + enum header_states h_state = (enum header_states) parser->header_state; + for (; p != data + len; p++) { + ch = *p; + if (ch == CR) { + UPDATE_STATE(s_header_almost_done); + parser->header_state = h_state; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_almost_done); + COUNT_HEADER_SIZE(p - start); + parser->header_state = h_state; + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + + if (!lenient && !IS_HEADER_CHAR(ch)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + c = LOWER(ch); + + switch (h_state) { + case h_general: + { + const char* p_cr; + const char* p_lf; + size_t limit = data + len - p; + + limit = MIN(limit, HTTP_MAX_HEADER_SIZE); + + p_cr = (const char*) memchr(p, CR, limit); + p_lf = (const char*) memchr(p, LF, limit); + if (p_cr != NULL) { + if (p_lf != NULL && p_cr >= p_lf) + p = p_lf; + else + p = p_cr; + } else if (UNLIKELY(p_lf != NULL)) { + p = p_lf; + } else { + p = data + len; + } + --p; + + break; + } + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + h_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + h_state = h_transfer_encoding_chunked; + } + break; + + case h_matching_connection_token_start: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + h_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + h_state = h_matching_connection_close; + } else if (c == 'u') { + h_state = h_matching_connection_upgrade; + } else if (STRICT_TOKEN(c)) { + h_state = h_matching_connection_token; + } else if (c == ' ' || c == '\t') { + /* Skip lws */ + } else { + h_state = h_general; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + h_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(CLOSE)-2) { + h_state = h_connection_close; + } + break; + + /* looking for 'Connection: upgrade' */ + case h_matching_connection_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE) - 1 || + c != UPGRADE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(UPGRADE)-2) { + h_state = h_connection_upgrade; + } + break; + + case h_matching_connection_token: + if (ch == ',') { + h_state = h_matching_connection_token_start; + parser->index = 0; + } + break; + + case h_transfer_encoding_chunked: + if (ch != ' ') h_state = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if (ch == ',') { + if (h_state == h_connection_keep_alive) { + parser->flags |= F_CONNECTION_KEEP_ALIVE; + } else if (h_state == h_connection_close) { + parser->flags |= F_CONNECTION_CLOSE; + } else if (h_state == h_connection_upgrade) { + parser->flags |= F_CONNECTION_UPGRADE; + } + h_state = h_matching_connection_token_start; + parser->index = 0; + } else if (ch != ' ') { + h_state = h_matching_connection_token; + } + break; + + default: + UPDATE_STATE(s_header_value); + h_state = h_general; + break; + } + } + parser->header_state = h_state; + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) + --p; + break; + } + + case s_header_almost_done: + { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_value_lws); + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_start); + REEXECUTE(); + } + + /* finished the header */ + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + default: + break; + } + + UPDATE_STATE(s_header_field_start); + REEXECUTE(); + } + + case s_header_value_discard_ws_almost_done: + { + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + case s_header_value_discard_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_discard_ws); + break; + } else { + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + /* header value was empty */ + MARK(header_value); + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + } + + case s_headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + UPDATE_STATE(s_message_done); + CALLBACK_NOTIFY_NOADVANCE(chunk_complete); + REEXECUTE(); + } + + /* Cannot use chunked encoding and a content-length header together + per the HTTP specification. */ + if ((parser->flags & F_CHUNKED) && + (parser->flags & F_CONTENTLENGTH)) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + + UPDATE_STATE(s_headers_done); + + /* Set this here so that on_headers_complete() callbacks can see it */ + if ((parser->flags & F_UPGRADE) && + (parser->flags & F_CONNECTION_UPGRADE)) { + /* For responses, "Upgrade: foo" and "Connection: upgrade" are + * mandatory only when it is a 101 Switching Protocols response, + * otherwise it is purely informational, to announce support. + */ + parser->upgrade = + (parser->type == HTTP_REQUEST || parser->status_code == 101); + } else { + parser->upgrade = (parser->method == HTTP_CONNECT); + } + + /* Here we call the headers_complete callback. This is somewhat + * different than other callbacks because if the user returns 1, we + * will interpret that as saying that this message has no body. This + * is needed for the annoying case of recieving a response to a HEAD + * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 2: + parser->upgrade = 1; + + /* FALLTHROUGH */ + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + RETURN(p - data); /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + RETURN(p - data); + } + + REEXECUTE(); + } + + case s_headers_done: + { + int hasBody; + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + hasBody = parser->flags & F_CHUNKED || + (parser->content_length > 0 && parser->content_length != ULLONG_MAX); + if (parser->upgrade && (parser->method == HTTP_CONNECT || + (parser->flags & F_SKIPBODY) || !hasBody)) { + /* Exit, the rest of the message is in a different protocol. */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + RETURN((p - data) + 1); + } + + if (parser->flags & F_SKIPBODY) { + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + UPDATE_STATE(s_chunk_size_start); + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + UPDATE_STATE(s_body_identity); + } else { + if (!http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + UPDATE_STATE(s_body_identity_eof); + } + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_message_done); + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + REEXECUTE(); + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + if (parser->upgrade) { + /* Exit, the rest of the message is in a different protocol. */ + RETURN((p - data) + 1); + } + break; + + case s_chunk_size_start: + { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (UNLIKELY(unhex_val == -1)) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + UPDATE_STATE(s_chunk_size); + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + UPDATE_STATE(s_chunk_parameters); + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + UPDATE_STATE(s_header_field_start); + } else { + UPDATE_STATE(s_chunk_data); + } + CALLBACK_NOTIFY(chunk_header); + break; + } + + case s_chunk_data: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_chunk_data_almost_done); + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + UPDATE_STATE(s_chunk_data_done); + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + UPDATE_STATE(s_chunk_size_start); + CALLBACK_NOTIFY(chunk_complete); + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0) + + (status_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + CALLBACK_DATA_NOADVANCE(status); + + RETURN(len); + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + RETURN(p - data); +} + + +/* Does the parser need to see an EOF to find the end of the message? */ +int +http_message_needs_eof (const http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (const http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !http_message_needs_eof(parser); +} + + +const char * +http_method_str (enum http_method m) +{ + return ELEM_AT(method_strings, m, "<unknown>"); +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +void +http_parser_settings_init(http_parser_settings *settings) +{ + memset(settings, 0, sizeof(*settings)); +} + +const char * +http_errno_name(enum http_errno err) { + assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab)); + return http_strerror_tab[err].description; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + if (s == s_http_host_v6 && ch == '%') { + return s_http_host_v6_zone_start; + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || + ch == '~') { + return s_http_host_v6_zone; + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + assert(u->field_set & (1 << UF_HOST)); + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +void +http_parser_url_init(struct http_parser_url *u) { + memset(u, 0, sizeof(*u)); +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* FALLTHROUGH */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & (1 << UF_SCHEMA)) && + (u->field_set & (1 << UF_HOST)) == 0) { + return 1; + } + + if (u->field_set & (1 << UF_HOST)) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} + +int +http_body_is_final(const struct http_parser *parser) { + return parser->state == s_message_done; +} + +unsigned long +http_parser_version(void) { + return HTTP_PARSER_VERSION_MAJOR * 0x10000 | + HTTP_PARSER_VERSION_MINOR * 0x00100 | + HTTP_PARSER_VERSION_PATCH * 0x00001; +} diff --git a/third-party/http-parser/http_parser.gyp b/third-party/http-parser/http_parser.gyp new file mode 100644 index 0000000..ef34eca --- /dev/null +++ b/third-party/http-parser/http_parser.gyp @@ -0,0 +1,111 @@ +# This file is used with the GYP meta build system. +# http://code.google.com/p/gyp/ +# To build try this: +# svn co http://gyp.googlecode.com/svn/trunk gyp +# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp +# ./out/Debug/test +{ + 'target_defaults': { + 'default_configuration': 'Debug', + 'configurations': { + # TODO: hoist these out and put them somewhere common, because + # RuntimeLibrary MUST MATCH across the entire project + 'Debug': { + 'defines': [ 'DEBUG', '_DEBUG' ], + 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 1, # static debug + }, + }, + }, + 'Release': { + 'defines': [ 'NDEBUG' ], + 'cflags': [ '-Wall', '-Wextra', '-O3' ], + 'msvs_settings': { + 'VCCLCompilerTool': { + 'RuntimeLibrary': 0, # static release + }, + }, + } + }, + 'msvs_settings': { + 'VCCLCompilerTool': { + }, + 'VCLibrarianTool': { + }, + 'VCLinkerTool': { + 'GenerateDebugInformation': 'true', + }, + }, + 'conditions': [ + ['OS == "win"', { + 'defines': [ + 'WIN32' + ], + }] + ], + }, + + 'targets': [ + { + 'target_name': 'http_parser', + 'type': 'static_library', + 'include_dirs': [ '.' ], + 'direct_dependent_settings': { + 'defines': [ 'HTTP_PARSER_STRICT=0' ], + 'include_dirs': [ '.' ], + }, + 'defines': [ 'HTTP_PARSER_STRICT=0' ], + 'sources': [ './http_parser.c', ], + 'conditions': [ + ['OS=="win"', { + 'msvs_settings': { + 'VCCLCompilerTool': { + # Compile as C++. http_parser.c is actually C99, but C++ is + # close enough in this case. + 'CompileAs': 2, + }, + }, + }] + ], + }, + + { + 'target_name': 'http_parser_strict', + 'type': 'static_library', + 'include_dirs': [ '.' ], + 'direct_dependent_settings': { + 'defines': [ 'HTTP_PARSER_STRICT=1' ], + 'include_dirs': [ '.' ], + }, + 'defines': [ 'HTTP_PARSER_STRICT=1' ], + 'sources': [ './http_parser.c', ], + 'conditions': [ + ['OS=="win"', { + 'msvs_settings': { + 'VCCLCompilerTool': { + # Compile as C++. http_parser.c is actually C99, but C++ is + # close enough in this case. + 'CompileAs': 2, + }, + }, + }] + ], + }, + + { + 'target_name': 'test-nonstrict', + 'type': 'executable', + 'dependencies': [ 'http_parser' ], + 'sources': [ 'test.c' ] + }, + + { + 'target_name': 'test-strict', + 'type': 'executable', + 'dependencies': [ 'http_parser_strict' ], + 'sources': [ 'test.c' ] + } + ] +} diff --git a/third-party/http-parser/http_parser.h b/third-party/http-parser/http_parser.h new file mode 100644 index 0000000..fd2a564 --- /dev/null +++ b/third-party/http-parser/http_parser.h @@ -0,0 +1,431 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef http_parser_h +#define http_parser_h +#ifdef __cplusplus +extern "C" { +#endif + +/* Also update SONAME in the Makefile whenever you change these. */ +#define HTTP_PARSER_VERSION_MAJOR 2 +#define HTTP_PARSER_VERSION_MINOR 7 +#define HTTP_PARSER_VERSION_PATCH 1 + +#include <stddef.h> +#if defined(_WIN32) && !defined(__MINGW32__) && \ + (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) +#include <BaseTsd.h> +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#else +#include <stdint.h> +#endif + +/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + +/* Maximium header size allowed. If the macro is not defined + * before including this header then the default is used. To + * change the maximum header size, define the macro in the build + * environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove + * the effective limit on the size of the header, define the macro + * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) + */ +#ifndef HTTP_MAX_HEADER_SIZE +# define HTTP_MAX_HEADER_SIZE (80*1024) +#endif + +typedef struct http_parser http_parser; +typedef struct http_parser_settings http_parser_settings; + + +/* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * Returning `2` from on_headers_complete will tell parser that it should not + * expect neither a body nor any futher responses on this connection. This is + * useful for handling responses to a CONNECT request which may not contain + * `Upgrade` or `Connection: upgrade` headers. + * + * http_data_cb does not return data chunks. It will be called arbitrarily + * many times for each string. E.G. you might get 10 callbacks for "on_url" + * each providing just a few characters more data. + */ +typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); +typedef int (*http_cb) (http_parser*); + + +/* Status Codes */ +#define HTTP_STATUS_MAP(XX) \ + XX(100, CONTINUE, Continue) \ + XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ + XX(102, PROCESSING, Processing) \ + XX(200, OK, OK) \ + XX(201, CREATED, Created) \ + XX(202, ACCEPTED, Accepted) \ + XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ + XX(204, NO_CONTENT, No Content) \ + XX(205, RESET_CONTENT, Reset Content) \ + XX(206, PARTIAL_CONTENT, Partial Content) \ + XX(207, MULTI_STATUS, Multi-Status) \ + XX(208, ALREADY_REPORTED, Already Reported) \ + XX(226, IM_USED, IM Used) \ + XX(300, MULTIPLE_CHOICES, Multiple Choices) \ + XX(301, MOVED_PERMANENTLY, Moved Permanently) \ + XX(302, FOUND, Found) \ + XX(303, SEE_OTHER, See Other) \ + XX(304, NOT_MODIFIED, Not Modified) \ + XX(305, USE_PROXY, Use Proxy) \ + XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ + XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ + XX(400, BAD_REQUEST, Bad Request) \ + XX(401, UNAUTHORIZED, Unauthorized) \ + XX(402, PAYMENT_REQUIRED, Payment Required) \ + XX(403, FORBIDDEN, Forbidden) \ + XX(404, NOT_FOUND, Not Found) \ + XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ + XX(406, NOT_ACCEPTABLE, Not Acceptable) \ + XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ + XX(408, REQUEST_TIMEOUT, Request Timeout) \ + XX(409, CONFLICT, Conflict) \ + XX(410, GONE, Gone) \ + XX(411, LENGTH_REQUIRED, Length Required) \ + XX(412, PRECONDITION_FAILED, Precondition Failed) \ + XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ + XX(414, URI_TOO_LONG, URI Too Long) \ + XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ + XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ + XX(417, EXPECTATION_FAILED, Expectation Failed) \ + XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ + XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ + XX(423, LOCKED, Locked) \ + XX(424, FAILED_DEPENDENCY, Failed Dependency) \ + XX(426, UPGRADE_REQUIRED, Upgrade Required) \ + XX(428, PRECONDITION_REQUIRED, Precondition Required) \ + XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ + XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ + XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ + XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ + XX(501, NOT_IMPLEMENTED, Not Implemented) \ + XX(502, BAD_GATEWAY, Bad Gateway) \ + XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ + XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ + XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ + XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ + XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ + XX(508, LOOP_DETECTED, Loop Detected) \ + XX(510, NOT_EXTENDED, Not Extended) \ + XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ + +enum http_status + { +#define XX(num, name, string) HTTP_STATUS_##name = num, + HTTP_STATUS_MAP(XX) +#undef XX + }; + + +/* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + /* pathological */ \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + /* WebDAV */ \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + XX(16, BIND, BIND) \ + XX(17, REBIND, REBIND) \ + XX(18, UNBIND, UNBIND) \ + XX(19, ACL, ACL) \ + /* subversion */ \ + XX(20, REPORT, REPORT) \ + XX(21, MKACTIVITY, MKACTIVITY) \ + XX(22, CHECKOUT, CHECKOUT) \ + XX(23, MERGE, MERGE) \ + /* upnp */ \ + XX(24, MSEARCH, M-SEARCH) \ + XX(25, NOTIFY, NOTIFY) \ + XX(26, SUBSCRIBE, SUBSCRIBE) \ + XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \ + /* RFC-5789 */ \ + XX(28, PATCH, PATCH) \ + XX(29, PURGE, PURGE) \ + /* CalDAV */ \ + XX(30, MKCALENDAR, MKCALENDAR) \ + /* RFC-2068, section 19.6.1.2 */ \ + XX(31, LINK, LINK) \ + XX(32, UNLINK, UNLINK) \ + +enum http_method + { +#define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + +enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + +/* Flag values for http_parser.flags field */ +enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_CONNECTION_UPGRADE = 1 << 3 + , F_TRAILING = 1 << 4 + , F_UPGRADE = 1 << 5 + , F_SKIPBODY = 1 << 6 + , F_CONTENTLENGTH = 1 << 7 + }; + + +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + XX(CB_status, "the on_status callback failed") \ + XX(CB_chunk_header, "the on_chunk_header callback failed") \ + XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(UNEXPECTED_CONTENT_LENGTH, \ + "unexpected content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + + +struct http_parser { + /** PRIVATE **/ + unsigned int type : 2; /* enum http_parser_type */ + unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ + unsigned int state : 7; /* enum state from http_parser.c */ + unsigned int header_state : 7; /* enum header_state from http_parser.c */ + unsigned int index : 7; /* index into current matcher */ + unsigned int lenient_http_headers : 1; + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned int status_code : 16; /* responses only */ + unsigned int method : 8; /* requests only */ + unsigned int http_errno : 7; + + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned int upgrade : 1; + + /** PUBLIC **/ + void *data; /* A pointer to get hook to the "connection" or "socket" object */ +}; + + +struct http_parser_settings { + http_cb on_message_begin; + http_data_cb on_url; + http_data_cb on_status; + http_data_cb on_header_field; + http_data_cb on_header_value; + http_cb on_headers_complete; + http_data_cb on_body; + http_cb on_message_complete; + /* When on_chunk_header is called, the current chunk length is stored + * in parser->content_length. + */ + http_cb on_chunk_header; + http_cb on_chunk_complete; +}; + + +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + + +/* Returns the library version. Bits 16-23 contain the major version number, + * bits 8-15 the minor version number and bits 0-7 the patch level. + * Usage example: + * + * unsigned long version = http_parser_version(); + * unsigned major = (version >> 16) & 255; + * unsigned minor = (version >> 8) & 255; + * unsigned patch = version & 255; + * printf("http_parser v%u.%u.%u\n", major, minor, patch); + */ +unsigned long http_parser_version(void); + +void http_parser_init(http_parser *parser, enum http_parser_type type); + + +/* Initialize http_parser_settings members to 0 + */ +void http_parser_settings_init(http_parser_settings *settings); + + +/* Executes the parser. Returns number of parsed bytes. Sets + * `parser->http_errno` on error. */ +size_t http_parser_execute(http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len); + + +/* If http_should_keep_alive() in the on_headers_complete or + * on_message_complete callback returns 0, then this should be + * the last message on the connection. + * If you are the server, respond with the "Connection: close" header. + * If you are the client, close the connection. + */ +int http_should_keep_alive(const http_parser *parser); + +/* Returns a string version of the HTTP method. */ +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); + +/* Initialize all http_parser_url members to 0 */ +void http_parser_url_init(struct http_parser_url *u); + +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); + +/* Checks if this is the final chunk of the body. */ +int http_body_is_final(const http_parser *parser); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/third-party/http-parser/test.c b/third-party/http-parser/test.c new file mode 100644 index 0000000..193004e --- /dev/null +++ b/third-party/http-parser/test.c @@ -0,0 +1,4411 @@ +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> /* rand */ +#include <string.h> +#include <stdarg.h> + +#if defined(__APPLE__) +# undef strlcat +# undef strlncpy +# undef strlcpy +#endif /* defined(__APPLE__) */ + +#undef TRUE +#define TRUE 1 +#undef FALSE +#define FALSE 0 + +#define MAX_HEADERS 13 +#define MAX_ELEMENT_SIZE 2048 +#define MAX_CHUNKS 16 + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +static http_parser *parser; + +struct message { + const char *name; // for debugging purposes + const char *raw; + enum http_parser_type type; + enum http_method method; + int status_code; + char response_status[MAX_ELEMENT_SIZE]; + char request_path[MAX_ELEMENT_SIZE]; + char request_url[MAX_ELEMENT_SIZE]; + char fragment[MAX_ELEMENT_SIZE]; + char query_string[MAX_ELEMENT_SIZE]; + char body[MAX_ELEMENT_SIZE]; + size_t body_size; + const char *host; + const char *userinfo; + uint16_t port; + int num_headers; + enum { NONE=0, FIELD, VALUE } last_header_element; + char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; + int should_keep_alive; + + int num_chunks; + int num_chunks_complete; + int chunk_lengths[MAX_CHUNKS]; + + const char *upgrade; // upgraded body + + unsigned short http_major; + unsigned short http_minor; + + int message_begin_cb_called; + int headers_complete_cb_called; + int message_complete_cb_called; + int status_cb_called; + int message_complete_on_eof; + int body_is_final; +}; + +static int currently_parsing_eof; + +static struct message messages[5]; +static int num_messages; +static http_parser_settings *current_pause_parser; + +/* * R E Q U E S T S * */ +const struct message requests[] = +#define CURL_GET 0 +{ {.name= "curl get" + ,.type= HTTP_REQUEST + ,.raw= "GET /test HTTP/1.1\r\n" + "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" + "Host: 0.0.0.0=5000\r\n" + "Accept: */*\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 3 + ,.headers= + { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" } + , { "Host", "0.0.0.0=5000" } + , { "Accept", "*/*" } + } + ,.body= "" + } + +#define FIREFOX_GET 1 +, {.name= "firefox get" + ,.type= HTTP_REQUEST + ,.raw= "GET /favicon.ico HTTP/1.1\r\n" + "Host: 0.0.0.0=5000\r\n" + "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + "Accept-Language: en-us,en;q=0.5\r\n" + "Accept-Encoding: gzip,deflate\r\n" + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + "Keep-Alive: 300\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/favicon.ico" + ,.request_url= "/favicon.ico" + ,.num_headers= 8 + ,.headers= + { { "Host", "0.0.0.0=5000" } + , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" } + , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" } + , { "Accept-Language", "en-us,en;q=0.5" } + , { "Accept-Encoding", "gzip,deflate" } + , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" } + , { "Keep-Alive", "300" } + , { "Connection", "keep-alive" } + } + ,.body= "" + } + +#define DUMBFUCK 2 +, {.name= "dumbfuck" + ,.type= HTTP_REQUEST + ,.raw= "GET /dumbfuck HTTP/1.1\r\n" + "aaaaaaaaaaaaa:++++++++++\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/dumbfuck" + ,.request_url= "/dumbfuck" + ,.num_headers= 1 + ,.headers= + { { "aaaaaaaaaaaaa", "++++++++++" } + } + ,.body= "" + } + +#define FRAGMENT_IN_URI 3 +, {.name= "fragment in url" + ,.type= HTTP_REQUEST + ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "page=1" + ,.fragment= "posts-17408" + ,.request_path= "/forums/1/topics/2375" + /* XXX request url does include fragment? */ + ,.request_url= "/forums/1/topics/2375?page=1#posts-17408" + ,.num_headers= 0 + ,.body= "" + } + +#define GET_NO_HEADERS_NO_BODY 4 +, {.name= "get no headers no body" + ,.type= HTTP_REQUEST + ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE /* would need Connection: close */ + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/get_no_headers_no_body/world" + ,.request_url= "/get_no_headers_no_body/world" + ,.num_headers= 0 + ,.body= "" + } + +#define GET_ONE_HEADER_NO_BODY 5 +, {.name= "get one header no body" + ,.type= HTTP_REQUEST + ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n" + "Accept: */*\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE /* would need Connection: close */ + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/get_one_header_no_body" + ,.request_url= "/get_one_header_no_body" + ,.num_headers= 1 + ,.headers= + { { "Accept" , "*/*" } + } + ,.body= "" + } + +#define GET_FUNKY_CONTENT_LENGTH 6 +, {.name= "get funky content length body hello" + ,.type= HTTP_REQUEST + ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" + "conTENT-Length: 5\r\n" + "\r\n" + "HELLO" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/get_funky_content_length_body_hello" + ,.request_url= "/get_funky_content_length_body_hello" + ,.num_headers= 1 + ,.headers= + { { "conTENT-Length" , "5" } + } + ,.body= "HELLO" + } + +#define POST_IDENTITY_BODY_WORLD 7 +, {.name= "post identity body world" + ,.type= HTTP_REQUEST + ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" + "Accept: */*\r\n" + "Transfer-Encoding: identity\r\n" + "Content-Length: 5\r\n" + "\r\n" + "World" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "q=search" + ,.fragment= "hey" + ,.request_path= "/post_identity_body_world" + ,.request_url= "/post_identity_body_world?q=search#hey" + ,.num_headers= 3 + ,.headers= + { { "Accept", "*/*" } + , { "Transfer-Encoding", "identity" } + , { "Content-Length", "5" } + } + ,.body= "World" + } + +#define POST_CHUNKED_ALL_YOUR_BASE 8 +, {.name= "post - chunked body: all your base are belong to us" + ,.type= HTTP_REQUEST + ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1e\r\nall your base are belong to us\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/post_chunked_all_your_base" + ,.request_url= "/post_chunked_all_your_base" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding" , "chunked" } + } + ,.body= "all your base are belong to us" + ,.num_chunks_complete= 2 + ,.chunk_lengths= { 0x1e } + } + +#define TWO_CHUNKS_MULT_ZERO_END 9 +, {.name= "two chunks ; triple zero ending" + ,.type= HTTP_REQUEST + ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\nhello\r\n" + "6\r\n world\r\n" + "000\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/two_chunks_mult_zero_end" + ,.request_url= "/two_chunks_mult_zero_end" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding", "chunked" } + } + ,.body= "hello world" + ,.num_chunks_complete= 3 + ,.chunk_lengths= { 5, 6 } + } + +#define CHUNKED_W_TRAILING_HEADERS 10 +, {.name= "chunked with trailing headers. blech." + ,.type= HTTP_REQUEST + ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5\r\nhello\r\n" + "6\r\n world\r\n" + "0\r\n" + "Vary: *\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/chunked_w_trailing_headers" + ,.request_url= "/chunked_w_trailing_headers" + ,.num_headers= 3 + ,.headers= + { { "Transfer-Encoding", "chunked" } + , { "Vary", "*" } + , { "Content-Type", "text/plain" } + } + ,.body= "hello world" + ,.num_chunks_complete= 3 + ,.chunk_lengths= { 5, 6 } + } + +#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11 +, {.name= "with bullshit after the length" + ,.type= HTTP_REQUEST + ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n" + "6; blahblah; blah\r\n world\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/chunked_w_bullshit_after_length" + ,.request_url= "/chunked_w_bullshit_after_length" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding", "chunked" } + } + ,.body= "hello world" + ,.num_chunks_complete= 3 + ,.chunk_lengths= { 5, 6 } + } + +#define WITH_QUOTES 12 +, {.name= "with quotes" + ,.type= HTTP_REQUEST + ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "foo=\"bar\"" + ,.fragment= "" + ,.request_path= "/with_\"stupid\"_quotes" + ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\"" + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define APACHEBENCH_GET 13 +/* The server receiving this request SHOULD NOT wait for EOF + * to know that content-length == 0. + * How to represent this in a unit test? message_complete_on_eof + * Compare with NO_CONTENT_LENGTH_RESPONSE. + */ +, {.name = "apachebench get" + ,.type= HTTP_REQUEST + ,.raw= "GET /test HTTP/1.0\r\n" + "Host: 0.0.0.0:5000\r\n" + "User-Agent: ApacheBench/2.3\r\n" + "Accept: */*\r\n\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 3 + ,.headers= { { "Host", "0.0.0.0:5000" } + , { "User-Agent", "ApacheBench/2.3" } + , { "Accept", "*/*" } + } + ,.body= "" + } + +#define QUERY_URL_WITH_QUESTION_MARK_GET 14 +/* Some clients include '?' characters in query strings. + */ +, {.name = "query url with question mark" + ,.type= HTTP_REQUEST + ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "foo=bar?baz" + ,.fragment= "" + ,.request_path= "/test.cgi" + ,.request_url= "/test.cgi?foo=bar?baz" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define PREFIX_NEWLINE_GET 15 +/* Some clients, especially after a POST in a keep-alive connection, + * will send an extra CRLF before the next request + */ +, {.name = "newline prefix get" + ,.type= HTTP_REQUEST + ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define UPGRADE_REQUEST 16 +, {.name = "upgrade request" + ,.type= HTTP_REQUEST + ,.raw= "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "\r\n" + "Hot diggity dogg" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/demo" + ,.request_url= "/demo" + ,.num_headers= 7 + ,.upgrade="Hot diggity dogg" + ,.headers= { { "Host", "example.com" } + , { "Connection", "Upgrade" } + , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } + , { "Sec-WebSocket-Protocol", "sample" } + , { "Upgrade", "WebSocket" } + , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } + , { "Origin", "http://example.com" } + } + ,.body= "" + } + +#define CONNECT_REQUEST 17 +, {.name = "connect request" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + "some data\r\n" + "and yet even more data" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "0-home0.netscape.com:443" + ,.num_headers= 2 + ,.upgrade="some data\r\nand yet even more data" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } + +#define REPORT_REQ 18 +, {.name= "report request" + ,.type= HTTP_REQUEST + ,.raw= "REPORT /test HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_REPORT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/test" + ,.request_url= "/test" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define NO_HTTP_VERSION 19 +, {.name= "request with no http version" + ,.type= HTTP_REQUEST + ,.raw= "GET /\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 0 + ,.http_minor= 9 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define MSEARCH_REQ 20 +, {.name= "m-search request" + ,.type= HTTP_REQUEST + ,.raw= "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "ST: \"ssdp:all\"\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_MSEARCH + ,.query_string= "" + ,.fragment= "" + ,.request_path= "*" + ,.request_url= "*" + ,.num_headers= 3 + ,.headers= { { "HOST", "239.255.255.250:1900" } + , { "MAN", "\"ssdp:discover\"" } + , { "ST", "\"ssdp:all\"" } + } + ,.body= "" + } + +#define LINE_FOLDING_IN_HEADER 21 +, {.name= "line folding in header value" + ,.type= HTTP_REQUEST + ,.raw= "GET / HTTP/1.1\r\n" + "Line1: abc\r\n" + "\tdef\r\n" + " ghi\r\n" + "\t\tjkl\r\n" + " mno \r\n" + "\t \tqrs\r\n" + "Line2: \t line2\t\r\n" + "Line3:\r\n" + " line3\r\n" + "Line4: \r\n" + " \r\n" + "Connection:\r\n" + " close\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 5 + ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } + , { "Line2", "line2\t" } + , { "Line3", "line3" } + , { "Line4", "" } + , { "Connection", "close" }, + } + ,.body= "" + } + + +#define QUERY_TERMINATED_HOST 22 +, {.name= "host terminated by a query string" + ,.type= HTTP_REQUEST + ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "hail=all" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "http://hypnotoad.org?hail=all" + ,.host= "hypnotoad.org" + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define QUERY_TERMINATED_HOSTPORT 23 +, {.name= "host:port terminated by a query string" + ,.type= HTTP_REQUEST + ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "hail=all" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "http://hypnotoad.org:1234?hail=all" + ,.host= "hypnotoad.org" + ,.port= 1234 + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define SPACE_TERMINATED_HOSTPORT 24 +, {.name= "host:port terminated by a space" + ,.type= HTTP_REQUEST + ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "http://hypnotoad.org:1234" + ,.host= "hypnotoad.org" + ,.port= 1234 + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define PATCH_REQ 25 +, {.name = "PATCH request" + ,.type= HTTP_REQUEST + ,.raw= "PATCH /file.txt HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/example\r\n" + "If-Match: \"e0023aa4e\"\r\n" + "Content-Length: 10\r\n" + "\r\n" + "cccccccccc" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_PATCH + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/file.txt" + ,.request_url= "/file.txt" + ,.num_headers= 4 + ,.headers= { { "Host", "www.example.com" } + , { "Content-Type", "application/example" } + , { "If-Match", "\"e0023aa4e\"" } + , { "Content-Length", "10" } + } + ,.body= "cccccccccc" + } + +#define CONNECT_CAPS_REQUEST 26 +, {.name = "connect caps request" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "HOME0.NETSCAPE.COM:443" + ,.num_headers= 2 + ,.upgrade="" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } + +#if !HTTP_PARSER_STRICT +#define UTF8_PATH_REQ 27 +, {.name= "utf-8 path request" + ,.type= HTTP_REQUEST + ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n" + "Host: github.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "q=1" + ,.fragment= "narf" + ,.request_path= "/δ¶/δt/pope" + ,.request_url= "/δ¶/δt/pope?q=1#narf" + ,.num_headers= 1 + ,.headers= { {"Host", "github.com" } + } + ,.body= "" + } + +#define HOSTNAME_UNDERSCORE 28 +, {.name = "hostname underscore" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.query_string= "" + ,.fragment= "" + ,.request_path= "" + ,.request_url= "home_0.netscape.com:443" + ,.num_headers= 2 + ,.upgrade="" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } +#endif /* !HTTP_PARSER_STRICT */ + +/* see https://github.com/ry/http-parser/issues/47 */ +#define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 29 +, {.name = "eat CRLF between requests, no \"Connection: close\" header" + ,.raw= "POST / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 4\r\n" + "\r\n" + "q=42\r\n" /* note the trailing CRLF */ + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 3 + ,.upgrade= 0 + ,.headers= { { "Host", "www.example.com" } + , { "Content-Type", "application/x-www-form-urlencoded" } + , { "Content-Length", "4" } + } + ,.body= "q=42" + } + +/* see https://github.com/ry/http-parser/issues/47 */ +#define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 30 +, {.name = "eat CRLF between requests even if \"Connection: close\" is set" + ,.raw= "POST / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 4\r\n" + "Connection: close\r\n" + "\r\n" + "q=42\r\n" /* note the trailing CRLF */ + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */ + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 4 + ,.upgrade= 0 + ,.headers= { { "Host", "www.example.com" } + , { "Content-Type", "application/x-www-form-urlencoded" } + , { "Content-Length", "4" } + , { "Connection", "close" } + } + ,.body= "q=42" + } + +#define PURGE_REQ 31 +, {.name = "PURGE request" + ,.type= HTTP_REQUEST + ,.raw= "PURGE /file.txt HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_PURGE + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/file.txt" + ,.request_url= "/file.txt" + ,.num_headers= 1 + ,.headers= { { "Host", "www.example.com" } } + ,.body= "" + } + +#define SEARCH_REQ 32 +, {.name = "SEARCH request" + ,.type= HTTP_REQUEST + ,.raw= "SEARCH / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_SEARCH + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 1 + ,.headers= { { "Host", "www.example.com" } } + ,.body= "" + } + +#define PROXY_WITH_BASIC_AUTH 33 +, {.name= "host:port and basic_auth" + ,.type= HTTP_REQUEST + ,.raw= "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.fragment= "" + ,.request_path= "/toto" + ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto" + ,.host= "hypnotoad.org" + ,.userinfo= "a%12:b!&*$" + ,.port= 1234 + ,.num_headers= 0 + ,.headers= { } + ,.body= "" + } + +#define LINE_FOLDING_IN_HEADER_WITH_LF 34 +, {.name= "line folding in header value" + ,.type= HTTP_REQUEST + ,.raw= "GET / HTTP/1.1\n" + "Line1: abc\n" + "\tdef\n" + " ghi\n" + "\t\tjkl\n" + " mno \n" + "\t \tqrs\n" + "Line2: \t line2\t\n" + "Line3:\n" + " line3\n" + "Line4: \n" + " \n" + "Connection:\n" + " close\n" + "\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 5 + ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } + , { "Line2", "line2\t" } + , { "Line3", "line3" } + , { "Line4", "" } + , { "Connection", "close" }, + } + ,.body= "" + } + +#define CONNECTION_MULTI 35 +, {.name = "multiple connection header values with folding" + ,.type= HTTP_REQUEST + ,.raw= "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Something,\r\n" + " Upgrade, ,Keep-Alive\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "\r\n" + "Hot diggity dogg" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/demo" + ,.request_url= "/demo" + ,.num_headers= 7 + ,.upgrade="Hot diggity dogg" + ,.headers= { { "Host", "example.com" } + , { "Connection", "Something, Upgrade, ,Keep-Alive" } + , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } + , { "Sec-WebSocket-Protocol", "sample" } + , { "Upgrade", "WebSocket" } + , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } + , { "Origin", "http://example.com" } + } + ,.body= "" + } + +#define CONNECTION_MULTI_LWS 36 +, {.name = "multiple connection header values with folding and lws" + ,.type= HTTP_REQUEST + ,.raw= "GET /demo HTTP/1.1\r\n" + "Connection: keep-alive, upgrade\r\n" + "Upgrade: WebSocket\r\n" + "\r\n" + "Hot diggity dogg" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/demo" + ,.request_url= "/demo" + ,.num_headers= 2 + ,.upgrade="Hot diggity dogg" + ,.headers= { { "Connection", "keep-alive, upgrade" } + , { "Upgrade", "WebSocket" } + } + ,.body= "" + } + +#define CONNECTION_MULTI_LWS_CRLF 37 +, {.name = "multiple connection header values with folding and lws" + ,.type= HTTP_REQUEST + ,.raw= "GET /demo HTTP/1.1\r\n" + "Connection: keep-alive, \r\n upgrade\r\n" + "Upgrade: WebSocket\r\n" + "\r\n" + "Hot diggity dogg" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/demo" + ,.request_url= "/demo" + ,.num_headers= 2 + ,.upgrade="Hot diggity dogg" + ,.headers= { { "Connection", "keep-alive, upgrade" } + , { "Upgrade", "WebSocket" } + } + ,.body= "" + } + +#define UPGRADE_POST_REQUEST 38 +, {.name = "upgrade post request" + ,.type= HTTP_REQUEST + ,.raw= "POST /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Upgrade: HTTP/2.0\r\n" + "Content-Length: 15\r\n" + "\r\n" + "sweet post body" + "Hot diggity dogg" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_POST + ,.request_path= "/demo" + ,.request_url= "/demo" + ,.num_headers= 4 + ,.upgrade="Hot diggity dogg" + ,.headers= { { "Host", "example.com" } + , { "Connection", "Upgrade" } + , { "Upgrade", "HTTP/2.0" } + , { "Content-Length", "15" } + } + ,.body= "sweet post body" + } + +#define CONNECT_WITH_BODY_REQUEST 39 +, {.name = "connect with body request" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT foo.bar.com:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "Content-Length: 10\r\n" + "\r\n" + "blarfcicle" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.request_url= "foo.bar.com:443" + ,.num_headers= 3 + ,.upgrade="blarfcicle" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + , { "Content-Length", "10" } + } + ,.body= "" + } + +/* Examples from the Internet draft for LINK/UNLINK methods: + * https://tools.ietf.org/id/draft-snell-link-method-01.html#rfc.section.5 + */ + +#define LINK_REQUEST 40 +, {.name = "link request" + ,.type= HTTP_REQUEST + ,.raw= "LINK /images/my_dog.jpg HTTP/1.1\r\n" + "Host: example.com\r\n" + "Link: <http://example.com/profiles/joe>; rel=\"tag\"\r\n" + "Link: <http://example.com/profiles/sally>; rel=\"tag\"\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_LINK + ,.request_path= "/images/my_dog.jpg" + ,.request_url= "/images/my_dog.jpg" + ,.query_string= "" + ,.fragment= "" + ,.num_headers= 3 + ,.headers= { { "Host", "example.com" } + , { "Link", "<http://example.com/profiles/joe>; rel=\"tag\"" } + , { "Link", "<http://example.com/profiles/sally>; rel=\"tag\"" } + } + ,.body= "" + } + +#define UNLINK_REQUEST 41 +, {.name = "unlink request" + ,.type= HTTP_REQUEST + ,.raw= "UNLINK /images/my_dog.jpg HTTP/1.1\r\n" + "Host: example.com\r\n" + "Link: <http://example.com/profiles/sally>; rel=\"tag\"\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_UNLINK + ,.request_path= "/images/my_dog.jpg" + ,.request_url= "/images/my_dog.jpg" + ,.query_string= "" + ,.fragment= "" + ,.num_headers= 2 + ,.headers= { { "Host", "example.com" } + , { "Link", "<http://example.com/profiles/sally>; rel=\"tag\"" } + } + ,.body= "" + } + +, {.name= NULL } /* sentinel */ +}; + +/* * R E S P O N S E S * */ +const struct message responses[] = +#define GOOGLE_301 0 +{ {.name= "google 301" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 301 Moved Permanently\r\n" + "Location: http://www.google.com/\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n" + "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n" + "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */ + "Cache-Control: public, max-age=2592000\r\n" + "Server: gws\r\n" + "Content-Length: 219 \r\n" + "\r\n" + "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n" + "<TITLE>301 Moved</TITLE></HEAD><BODY>\n" + "<H1>301 Moved</H1>\n" + "The document has moved\n" + "<A HREF=\"http://www.google.com/\">here</A>.\r\n" + "</BODY></HTML>\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 301 + ,.response_status= "Moved Permanently" + ,.num_headers= 8 + ,.headers= + { { "Location", "http://www.google.com/" } + , { "Content-Type", "text/html; charset=UTF-8" } + , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" } + , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" } + , { "X-$PrototypeBI-Version", "1.6.0.3" } + , { "Cache-Control", "public, max-age=2592000" } + , { "Server", "gws" } + , { "Content-Length", "219 " } + } + ,.body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n" + "<TITLE>301 Moved</TITLE></HEAD><BODY>\n" + "<H1>301 Moved</H1>\n" + "The document has moved\n" + "<A HREF=\"http://www.google.com/\">here</A>.\r\n" + "</BODY></HTML>\r\n" + } + +#define NO_CONTENT_LENGTH_RESPONSE 1 +/* The client should wait for the server's EOF. That is, when content-length + * is not specified, and "Connection: close", the end of body is specified + * by the EOF. + * Compare with APACHEBENCH_GET + */ +, {.name= "no content-length response" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n" + "Server: Apache\r\n" + "X-Powered-By: Servlet/2.5 JSP/2.1\r\n" + "Content-Type: text/xml; charset=utf-8\r\n" + "Connection: close\r\n" + "\r\n" + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" + " <SOAP-ENV:Body>\n" + " <SOAP-ENV:Fault>\n" + " <faultcode>SOAP-ENV:Client</faultcode>\n" + " <faultstring>Client Error</faultstring>\n" + " </SOAP-ENV:Fault>\n" + " </SOAP-ENV:Body>\n" + "</SOAP-ENV:Envelope>" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 5 + ,.headers= + { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" } + , { "Server", "Apache" } + , { "X-Powered-By", "Servlet/2.5 JSP/2.1" } + , { "Content-Type", "text/xml; charset=utf-8" } + , { "Connection", "close" } + } + ,.body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n" + " <SOAP-ENV:Body>\n" + " <SOAP-ENV:Fault>\n" + " <faultcode>SOAP-ENV:Client</faultcode>\n" + " <faultstring>Client Error</faultstring>\n" + " </SOAP-ENV:Fault>\n" + " </SOAP-ENV:Body>\n" + "</SOAP-ENV:Envelope>" + } + +#define NO_HEADERS_NO_BODY_404 2 +, {.name= "404 no headers no body" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 404 + ,.response_status= "Not Found" + ,.num_headers= 0 + ,.headers= {} + ,.body_size= 0 + ,.body= "" + } + +#define NO_REASON_PHRASE 3 +, {.name= "301 no response phrase" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 301\r\n\r\n" + ,.should_keep_alive = FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 301 + ,.response_status= "" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define TRAILING_SPACE_ON_CHUNKED_BODY 4 +, {.name="200 trailing space on chunked body" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "25 \r\n" + "This is the data in the first chunk\r\n" + "\r\n" + "1C\r\n" + "and this is the second one\r\n" + "\r\n" + "0 \r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 2 + ,.headers= + { {"Content-Type", "text/plain" } + , {"Transfer-Encoding", "chunked" } + } + ,.body_size = 37+28 + ,.body = + "This is the data in the first chunk\r\n" + "and this is the second one\r\n" + ,.num_chunks_complete= 3 + ,.chunk_lengths= { 0x25, 0x1c } + } + +#define NO_CARRIAGE_RET 5 +, {.name="no carriage ret" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\n" + "Content-Type: text/html; charset=utf-8\n" + "Connection: close\n" + "\n" + "these headers are from http://news.ycombinator.com/" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 2 + ,.headers= + { {"Content-Type", "text/html; charset=utf-8" } + , {"Connection", "close" } + } + ,.body= "these headers are from http://news.ycombinator.com/" + } + +#define PROXY_CONNECTION 6 +, {.name="proxy connection" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Content-Length: 11\r\n" + "Proxy-Connection: close\r\n" + "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n" + "\r\n" + "hello world" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 4 + ,.headers= + { {"Content-Type", "text/html; charset=UTF-8" } + , {"Content-Length", "11" } + , {"Proxy-Connection", "close" } + , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"} + } + ,.body= "hello world" + } + +#define UNDERSTORE_HEADER_KEY 7 + // shown by + // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;" +, {.name="underscore header key" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Server: DCLK-AdSvr\r\n" + "Content-Type: text/xml\r\n" + "Content-Length: 0\r\n" + "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 4 + ,.headers= + { {"Server", "DCLK-AdSvr" } + , {"Content-Type", "text/xml" } + , {"Content-Length", "0" } + , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" } + } + ,.body= "" + } + +#define BONJOUR_MADAME_FR 8 +/* The client should not merge two headers fields when the first one doesn't + * have a value. + */ +, {.name= "bonjourmadame.fr" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.0 301 Moved Permanently\r\n" + "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n" + "Server: Apache/2.2.3 (Red Hat)\r\n" + "Cache-Control: public\r\n" + "Pragma: \r\n" + "Location: http://www.bonjourmadame.fr/\r\n" + "Vary: Accept-Encoding\r\n" + "Content-Length: 0\r\n" + "Content-Type: text/html; charset=UTF-8\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 301 + ,.response_status= "Moved Permanently" + ,.num_headers= 9 + ,.headers= + { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" } + , { "Server", "Apache/2.2.3 (Red Hat)" } + , { "Cache-Control", "public" } + , { "Pragma", "" } + , { "Location", "http://www.bonjourmadame.fr/" } + , { "Vary", "Accept-Encoding" } + , { "Content-Length", "0" } + , { "Content-Type", "text/html; charset=UTF-8" } + , { "Connection", "keep-alive" } + } + ,.body= "" + } + +#define RES_FIELD_UNDERSCORE 9 +/* Should handle spaces in header fields */ +, {.name= "field underscore" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n" + "Server: Apache\r\n" + "Cache-Control: no-cache, must-revalidate\r\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n" + "Vary: Accept-Encoding\r\n" + "_eep-Alive: timeout=45\r\n" /* semantic value ignored */ + "_onnection: Keep-Alive\r\n" /* semantic value ignored */ + "Transfer-Encoding: chunked\r\n" + "Content-Type: text/html\r\n" + "Connection: close\r\n" + "\r\n" + "0\r\n\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 11 + ,.headers= + { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" } + , { "Server", "Apache" } + , { "Cache-Control", "no-cache, must-revalidate" } + , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" } + , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" } + , { "Vary", "Accept-Encoding" } + , { "_eep-Alive", "timeout=45" } + , { "_onnection", "Keep-Alive" } + , { "Transfer-Encoding", "chunked" } + , { "Content-Type", "text/html" } + , { "Connection", "close" } + } + ,.body= "" + ,.num_chunks_complete= 1 + ,.chunk_lengths= {} + } + +#define NON_ASCII_IN_STATUS_LINE 10 +/* Should handle non-ASCII in status line */ +, {.name= "non-ASCII in status line" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n" + "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 500 + ,.response_status= "Oriëntatieprobleem" + ,.num_headers= 3 + ,.headers= + { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" } + , { "Content-Length", "0" } + , { "Connection", "close" } + } + ,.body= "" + } + +#define HTTP_VERSION_0_9 11 +/* Should handle HTTP/0.9 */ +, {.name= "http version 0.9" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/0.9 200 OK\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 0 + ,.http_minor= 9 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 0 + ,.headers= + {} + ,.body= "" + } + +#define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12 +/* The client should wait for the server's EOF. That is, when neither + * content-length nor transfer-encoding is specified, the end of body + * is specified by the EOF. + */ +, {.name= "neither content-length nor transfer-encoding response" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "hello world" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 1 + ,.headers= + { { "Content-Type", "text/plain" } + } + ,.body= "hello world" + } + +#define NO_BODY_HTTP10_KA_200 13 +, {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.0 200 OK\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 1 + ,.headers= + { { "Connection", "keep-alive" } + } + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP10_KA_204 14 +, {.name= "HTTP/1.0 with keep-alive and a 204 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.0 204 No content\r\n" + "Connection: keep-alive\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 204 + ,.response_status= "No content" + ,.num_headers= 1 + ,.headers= + { { "Connection", "keep-alive" } + } + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_KA_200 15 +, {.name= "HTTP/1.1 with an EOF-terminated 200 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 0 + ,.headers={} + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_KA_204 16 +, {.name= "HTTP/1.1 with a 204 status" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 204 No content\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 204 + ,.response_status= "No content" + ,.num_headers= 0 + ,.headers={} + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_NOKA_204 17 +, {.name= "HTTP/1.1 with a 204 status and keep-alive disabled" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 204 No content\r\n" + "Connection: close\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 204 + ,.response_status= "No content" + ,.num_headers= 1 + ,.headers= + { { "Connection", "close" } + } + ,.body_size= 0 + ,.body= "" + } + +#define NO_BODY_HTTP11_KA_CHUNKED_200 18 +, {.name= "HTTP/1.1 with chunked endocing and a 200 response" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 1 + ,.headers= + { { "Transfer-Encoding", "chunked" } + } + ,.body_size= 0 + ,.body= "" + ,.num_chunks_complete= 1 + } + +#if !HTTP_PARSER_STRICT +#define SPACE_IN_FIELD_RES 19 +/* Should handle spaces in header fields */ +, {.name= "field space" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Server: Microsoft-IIS/6.0\r\n" + "X-Powered-By: ASP.NET\r\n" + "en-US Content-Type: text/xml\r\n" /* this is the problem */ + "Content-Type: text/xml\r\n" + "Content-Length: 16\r\n" + "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n" + "Connection: keep-alive\r\n" + "\r\n" + "<xml>hello</xml>" /* fake body */ + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 7 + ,.headers= + { { "Server", "Microsoft-IIS/6.0" } + , { "X-Powered-By", "ASP.NET" } + , { "en-US Content-Type", "text/xml" } + , { "Content-Type", "text/xml" } + , { "Content-Length", "16" } + , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" } + , { "Connection", "keep-alive" } + } + ,.body= "<xml>hello</xml>" + } +#endif /* !HTTP_PARSER_STRICT */ + +#define AMAZON_COM 20 +, {.name= "amazon.com" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 301 MovedPermanently\r\n" + "Date: Wed, 15 May 2013 17:06:33 GMT\r\n" + "Server: Server\r\n" + "x-amz-id-1: 0GPHKXSJQ826RK7GZEB2\r\n" + "p3p: policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"\r\n" + "x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD\r\n" + "Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846\r\n" + "Vary: Accept-Encoding,User-Agent\r\n" + "Content-Type: text/html; charset=ISO-8859-1\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "1\r\n" + "\n\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 301 + ,.response_status= "MovedPermanently" + ,.num_headers= 9 + ,.headers= { { "Date", "Wed, 15 May 2013 17:06:33 GMT" } + , { "Server", "Server" } + , { "x-amz-id-1", "0GPHKXSJQ826RK7GZEB2" } + , { "p3p", "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" } + , { "x-amz-id-2", "STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD" } + , { "Location", "http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846" } + , { "Vary", "Accept-Encoding,User-Agent" } + , { "Content-Type", "text/html; charset=ISO-8859-1" } + , { "Transfer-Encoding", "chunked" } + } + ,.body= "\n" + ,.num_chunks_complete= 2 + ,.chunk_lengths= { 1 } + } + +#define EMPTY_REASON_PHRASE_AFTER_SPACE 20 +, {.name= "empty reason phrase after space" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 \r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "" + ,.num_headers= 0 + ,.headers= {} + ,.body= "" + } + +#define CONTENT_LENGTH_X 21 +, {.name= "Content-Length-X" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Content-Length-X: 0\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "2\r\n" + "OK\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 2 + ,.headers= { { "Content-Length-X", "0" } + , { "Transfer-Encoding", "chunked" } + } + ,.body= "OK" + ,.num_chunks_complete= 2 + ,.chunk_lengths= { 2 } + } + +#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER 22 +, {.name= "HTTP 101 response with Upgrade header" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 101 Switching Protocols\r\n" + "Connection: upgrade\r\n" + "Upgrade: h2c\r\n" + "\r\n" + "proto" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 101 + ,.response_status= "Switching Protocols" + ,.upgrade= "proto" + ,.num_headers= 2 + ,.headers= + { { "Connection", "upgrade" } + , { "Upgrade", "h2c" } + } + } + +#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 23 +, {.name= "HTTP 101 response with Upgrade and Content-Length header" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 101 Switching Protocols\r\n" + "Connection: upgrade\r\n" + "Upgrade: h2c\r\n" + "Content-Length: 4\r\n" + "\r\n" + "body" + "proto" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 101 + ,.response_status= "Switching Protocols" + ,.body= "body" + ,.upgrade= "proto" + ,.num_headers= 3 + ,.headers= + { { "Connection", "upgrade" } + , { "Upgrade", "h2c" } + , { "Content-Length", "4" } + } + } + +#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 24 +, {.name= "HTTP 101 response with Upgrade and Transfer-Encoding header" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 101 Switching Protocols\r\n" + "Connection: upgrade\r\n" + "Upgrade: h2c\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "2\r\n" + "bo\r\n" + "2\r\n" + "dy\r\n" + "0\r\n" + "\r\n" + "proto" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 101 + ,.response_status= "Switching Protocols" + ,.body= "body" + ,.upgrade= "proto" + ,.num_headers= 3 + ,.headers= + { { "Connection", "upgrade" } + , { "Upgrade", "h2c" } + , { "Transfer-Encoding", "chunked" } + } + ,.num_chunks_complete= 3 + ,.chunk_lengths= { 2, 2 } + } + +#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER 25 +, {.name= "HTTP 200 response with Upgrade header" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Connection: upgrade\r\n" + "Upgrade: h2c\r\n" + "\r\n" + "body" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= TRUE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.body= "body" + ,.upgrade= NULL + ,.num_headers= 2 + ,.headers= + { { "Connection", "upgrade" } + , { "Upgrade", "h2c" } + } + } + +#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 26 +, {.name= "HTTP 200 response with Upgrade and Content-Length header" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Connection: upgrade\r\n" + "Upgrade: h2c\r\n" + "Content-Length: 4\r\n" + "\r\n" + "body" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 3 + ,.body= "body" + ,.upgrade= NULL + ,.headers= + { { "Connection", "upgrade" } + , { "Upgrade", "h2c" } + , { "Content-Length", "4" } + } + } + +#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 27 +, {.name= "HTTP 200 response with Upgrade and Transfer-Encoding header" + ,.type= HTTP_RESPONSE + ,.raw= "HTTP/1.1 200 OK\r\n" + "Connection: upgrade\r\n" + "Upgrade: h2c\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "2\r\n" + "bo\r\n" + "2\r\n" + "dy\r\n" + "0\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 3 + ,.body= "body" + ,.upgrade= NULL + ,.headers= + { { "Connection", "upgrade" } + , { "Upgrade", "h2c" } + , { "Transfer-Encoding", "chunked" } + } + ,.num_chunks_complete= 3 + ,.chunk_lengths= { 2, 2 } + } + +, {.name= NULL } /* sentinel */ +}; + +/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so + * define it ourselves. + */ +size_t +strnlen(const char *s, size_t maxlen) +{ + const char *p; + + p = memchr(s, '\0', maxlen); + if (p == NULL) + return maxlen; + + return p - s; +} + +size_t +strlncat(char *dst, size_t len, const char *src, size_t n) +{ + size_t slen; + size_t dlen; + size_t rlen; + size_t ncpy; + + slen = strnlen(src, n); + dlen = strnlen(dst, len); + + if (dlen < len) { + rlen = len - dlen; + ncpy = slen < rlen ? slen : (rlen - 1); + memcpy(dst + dlen, src, ncpy); + dst[dlen + ncpy] = '\0'; + } + + assert(len > slen + dlen); + return slen + dlen; +} + +size_t +strlcat(char *dst, const char *src, size_t len) +{ + return strlncat(dst, len, src, (size_t) -1); +} + +size_t +strlncpy(char *dst, size_t len, const char *src, size_t n) +{ + size_t slen; + size_t ncpy; + + slen = strnlen(src, n); + + if (len > 0) { + ncpy = slen < len ? slen : (len - 1); + memcpy(dst, src, ncpy); + dst[ncpy] = '\0'; + } + + assert(len > slen); + return slen; +} + +size_t +strlcpy(char *dst, const char *src, size_t len) +{ + return strlncpy(dst, len, src, (size_t) -1); +} + +int +request_url_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + strlncat(messages[num_messages].request_url, + sizeof(messages[num_messages].request_url), + buf, + len); + return 0; +} + +int +header_field_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + struct message *m = &messages[num_messages]; + + if (m->last_header_element != FIELD) + m->num_headers++; + + strlncat(m->headers[m->num_headers-1][0], + sizeof(m->headers[m->num_headers-1][0]), + buf, + len); + + m->last_header_element = FIELD; + + return 0; +} + +int +header_value_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + struct message *m = &messages[num_messages]; + + strlncat(m->headers[m->num_headers-1][1], + sizeof(m->headers[m->num_headers-1][1]), + buf, + len); + + m->last_header_element = VALUE; + + return 0; +} + +void +check_body_is_final (const http_parser *p) +{ + if (messages[num_messages].body_is_final) { + fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " + "on last on_body callback call " + "but it doesn't! ***\n\n"); + assert(0); + abort(); + } + messages[num_messages].body_is_final = http_body_is_final(p); +} + +int +body_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + strlncat(messages[num_messages].body, + sizeof(messages[num_messages].body), + buf, + len); + messages[num_messages].body_size += len; + check_body_is_final(p); + // printf("body_cb: '%s'\n", requests[num_messages].body); + return 0; +} + +int +count_body_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + assert(buf); + messages[num_messages].body_size += len; + check_body_is_final(p); + return 0; +} + +int +message_begin_cb (http_parser *p) +{ + assert(p == parser); + messages[num_messages].message_begin_cb_called = TRUE; + return 0; +} + +int +headers_complete_cb (http_parser *p) +{ + assert(p == parser); + messages[num_messages].method = parser->method; + messages[num_messages].status_code = parser->status_code; + messages[num_messages].http_major = parser->http_major; + messages[num_messages].http_minor = parser->http_minor; + messages[num_messages].headers_complete_cb_called = TRUE; + messages[num_messages].should_keep_alive = http_should_keep_alive(parser); + return 0; +} + +int +message_complete_cb (http_parser *p) +{ + assert(p == parser); + if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser)) + { + fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same " + "value in both on_message_complete and on_headers_complete " + "but it doesn't! ***\n\n"); + assert(0); + abort(); + } + + if (messages[num_messages].body_size && + http_body_is_final(p) && + !messages[num_messages].body_is_final) + { + fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " + "on last on_body callback call " + "but it doesn't! ***\n\n"); + assert(0); + abort(); + } + + messages[num_messages].message_complete_cb_called = TRUE; + + messages[num_messages].message_complete_on_eof = currently_parsing_eof; + + num_messages++; + return 0; +} + +int +response_status_cb (http_parser *p, const char *buf, size_t len) +{ + assert(p == parser); + + messages[num_messages].status_cb_called = TRUE; + + strlncat(messages[num_messages].response_status, + sizeof(messages[num_messages].response_status), + buf, + len); + return 0; +} + +int +chunk_header_cb (http_parser *p) +{ + assert(p == parser); + int chunk_idx = messages[num_messages].num_chunks; + messages[num_messages].num_chunks++; + if (chunk_idx < MAX_CHUNKS) { + messages[num_messages].chunk_lengths[chunk_idx] = p->content_length; + } + + return 0; +} + +int +chunk_complete_cb (http_parser *p) +{ + assert(p == parser); + + /* Here we want to verify that each chunk_header_cb is matched by a + * chunk_complete_cb, so not only should the total number of calls to + * both callbacks be the same, but they also should be interleaved + * properly */ + assert(messages[num_messages].num_chunks == + messages[num_messages].num_chunks_complete + 1); + + messages[num_messages].num_chunks_complete++; + return 0; +} + +/* These dontcall_* callbacks exist so that we can verify that when we're + * paused, no additional callbacks are invoked */ +int +dontcall_message_begin_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_header_field_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_header_value_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_request_url_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_body_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_headers_complete_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_headers_complete() called on paused " + "parser ***\n\n"); + abort(); +} + +int +dontcall_message_complete_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_message_complete() called on paused " + "parser ***\n\n"); + abort(); +} + +int +dontcall_response_status_cb (http_parser *p, const char *buf, size_t len) +{ + if (p || buf || len) { } // gcc + fprintf(stderr, "\n\n*** on_status() called on paused parser ***\n\n"); + abort(); +} + +int +dontcall_chunk_header_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_chunk_header() called on paused parser ***\n\n"); + exit(1); +} + +int +dontcall_chunk_complete_cb (http_parser *p) +{ + if (p) { } // gcc + fprintf(stderr, "\n\n*** on_chunk_complete() " + "called on paused parser ***\n\n"); + exit(1); +} + +static http_parser_settings settings_dontcall = + {.on_message_begin = dontcall_message_begin_cb + ,.on_header_field = dontcall_header_field_cb + ,.on_header_value = dontcall_header_value_cb + ,.on_url = dontcall_request_url_cb + ,.on_status = dontcall_response_status_cb + ,.on_body = dontcall_body_cb + ,.on_headers_complete = dontcall_headers_complete_cb + ,.on_message_complete = dontcall_message_complete_cb + ,.on_chunk_header = dontcall_chunk_header_cb + ,.on_chunk_complete = dontcall_chunk_complete_cb + }; + +/* These pause_* callbacks always pause the parser and just invoke the regular + * callback that tracks content. Before returning, we overwrite the parser + * settings to point to the _dontcall variety so that we can verify that + * the pause actually did, you know, pause. */ +int +pause_message_begin_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return message_begin_cb(p); +} + +int +pause_header_field_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return header_field_cb(p, buf, len); +} + +int +pause_header_value_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return header_value_cb(p, buf, len); +} + +int +pause_request_url_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return request_url_cb(p, buf, len); +} + +int +pause_body_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return body_cb(p, buf, len); +} + +int +pause_headers_complete_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return headers_complete_cb(p); +} + +int +pause_message_complete_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return message_complete_cb(p); +} + +int +pause_response_status_cb (http_parser *p, const char *buf, size_t len) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return response_status_cb(p, buf, len); +} + +int +pause_chunk_header_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return chunk_header_cb(p); +} + +int +pause_chunk_complete_cb (http_parser *p) +{ + http_parser_pause(p, 1); + *current_pause_parser = settings_dontcall; + return chunk_complete_cb(p); +} + +int +connect_headers_complete_cb (http_parser *p) +{ + headers_complete_cb(p); + return 1; +} + +int +connect_message_complete_cb (http_parser *p) +{ + messages[num_messages].should_keep_alive = http_should_keep_alive(parser); + return message_complete_cb(p); +} + +static http_parser_settings settings_pause = + {.on_message_begin = pause_message_begin_cb + ,.on_header_field = pause_header_field_cb + ,.on_header_value = pause_header_value_cb + ,.on_url = pause_request_url_cb + ,.on_status = pause_response_status_cb + ,.on_body = pause_body_cb + ,.on_headers_complete = pause_headers_complete_cb + ,.on_message_complete = pause_message_complete_cb + ,.on_chunk_header = pause_chunk_header_cb + ,.on_chunk_complete = pause_chunk_complete_cb + }; + +static http_parser_settings settings = + {.on_message_begin = message_begin_cb + ,.on_header_field = header_field_cb + ,.on_header_value = header_value_cb + ,.on_url = request_url_cb + ,.on_status = response_status_cb + ,.on_body = body_cb + ,.on_headers_complete = headers_complete_cb + ,.on_message_complete = message_complete_cb + ,.on_chunk_header = chunk_header_cb + ,.on_chunk_complete = chunk_complete_cb + }; + +static http_parser_settings settings_count_body = + {.on_message_begin = message_begin_cb + ,.on_header_field = header_field_cb + ,.on_header_value = header_value_cb + ,.on_url = request_url_cb + ,.on_status = response_status_cb + ,.on_body = count_body_cb + ,.on_headers_complete = headers_complete_cb + ,.on_message_complete = message_complete_cb + ,.on_chunk_header = chunk_header_cb + ,.on_chunk_complete = chunk_complete_cb + }; + +static http_parser_settings settings_connect = + {.on_message_begin = message_begin_cb + ,.on_header_field = header_field_cb + ,.on_header_value = header_value_cb + ,.on_url = request_url_cb + ,.on_status = response_status_cb + ,.on_body = dontcall_body_cb + ,.on_headers_complete = connect_headers_complete_cb + ,.on_message_complete = connect_message_complete_cb + ,.on_chunk_header = chunk_header_cb + ,.on_chunk_complete = chunk_complete_cb + }; + +static http_parser_settings settings_null = + {.on_message_begin = 0 + ,.on_header_field = 0 + ,.on_header_value = 0 + ,.on_url = 0 + ,.on_status = 0 + ,.on_body = 0 + ,.on_headers_complete = 0 + ,.on_message_complete = 0 + ,.on_chunk_header = 0 + ,.on_chunk_complete = 0 + }; + +void +parser_init (enum http_parser_type type) +{ + num_messages = 0; + + assert(parser == NULL); + + parser = malloc(sizeof(http_parser)); + + http_parser_init(parser, type); + + memset(&messages, 0, sizeof messages); + +} + +void +parser_free () +{ + assert(parser); + free(parser); + parser = NULL; +} + +size_t parse (const char *buf, size_t len) +{ + size_t nparsed; + currently_parsing_eof = (len == 0); + nparsed = http_parser_execute(parser, &settings, buf, len); + return nparsed; +} + +size_t parse_count_body (const char *buf, size_t len) +{ + size_t nparsed; + currently_parsing_eof = (len == 0); + nparsed = http_parser_execute(parser, &settings_count_body, buf, len); + return nparsed; +} + +size_t parse_pause (const char *buf, size_t len) +{ + size_t nparsed; + http_parser_settings s = settings_pause; + + currently_parsing_eof = (len == 0); + current_pause_parser = &s; + nparsed = http_parser_execute(parser, current_pause_parser, buf, len); + return nparsed; +} + +size_t parse_connect (const char *buf, size_t len) +{ + size_t nparsed; + currently_parsing_eof = (len == 0); + nparsed = http_parser_execute(parser, &settings_connect, buf, len); + return nparsed; +} + +static inline int +check_str_eq (const struct message *m, + const char *prop, + const char *expected, + const char *found) { + if ((expected == NULL) != (found == NULL)) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected %s\n", (expected == NULL) ? "NULL" : expected); + printf(" found %s\n", (found == NULL) ? "NULL" : found); + return 0; + } + if (expected != NULL && 0 != strcmp(expected, found)) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected '%s'\n", expected); + printf(" found '%s'\n", found); + return 0; + } + return 1; +} + +static inline int +check_num_eq (const struct message *m, + const char *prop, + int expected, + int found) { + if (expected != found) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected %d\n", expected); + printf(" found %d\n", found); + return 0; + } + return 1; +} + +#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \ + if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0 + +#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \ + if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0 + +#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn) \ +do { \ + char ubuf[256]; \ + \ + if ((u)->field_set & (1 << (fn))) { \ + memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off, \ + (u)->field_data[(fn)].len); \ + ubuf[(u)->field_data[(fn)].len] = '\0'; \ + } else { \ + ubuf[0] = '\0'; \ + } \ + \ + check_str_eq(expected, #prop, expected->prop, ubuf); \ +} while(0) + +int +message_eq (int index, int connect, const struct message *expected) +{ + int i; + struct message *m = &messages[index]; + + MESSAGE_CHECK_NUM_EQ(expected, m, http_major); + MESSAGE_CHECK_NUM_EQ(expected, m, http_minor); + + if (expected->type == HTTP_REQUEST) { + MESSAGE_CHECK_NUM_EQ(expected, m, method); + } else { + MESSAGE_CHECK_NUM_EQ(expected, m, status_code); + MESSAGE_CHECK_STR_EQ(expected, m, response_status); + assert(m->status_cb_called); + } + + if (!connect) { + MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive); + MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof); + } + + assert(m->message_begin_cb_called); + assert(m->headers_complete_cb_called); + assert(m->message_complete_cb_called); + + + MESSAGE_CHECK_STR_EQ(expected, m, request_url); + + /* Check URL components; we can't do this w/ CONNECT since it doesn't + * send us a well-formed URL. + */ + if (*m->request_url && m->method != HTTP_CONNECT) { + struct http_parser_url u; + + if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) { + fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n", + m->request_url); + abort(); + } + + if (expected->host) { + MESSAGE_CHECK_URL_EQ(&u, expected, m, host, UF_HOST); + } + + if (expected->userinfo) { + MESSAGE_CHECK_URL_EQ(&u, expected, m, userinfo, UF_USERINFO); + } + + m->port = (u.field_set & (1 << UF_PORT)) ? + u.port : 0; + + MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY); + MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT); + MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH); + MESSAGE_CHECK_NUM_EQ(expected, m, port); + } + + if (connect) { + check_num_eq(m, "body_size", 0, m->body_size); + } else if (expected->body_size) { + MESSAGE_CHECK_NUM_EQ(expected, m, body_size); + } else { + MESSAGE_CHECK_STR_EQ(expected, m, body); + } + + if (connect) { + check_num_eq(m, "num_chunks_complete", 0, m->num_chunks_complete); + } else { + assert(m->num_chunks == m->num_chunks_complete); + MESSAGE_CHECK_NUM_EQ(expected, m, num_chunks_complete); + for (i = 0; i < m->num_chunks && i < MAX_CHUNKS; i++) { + MESSAGE_CHECK_NUM_EQ(expected, m, chunk_lengths[i]); + } + } + + MESSAGE_CHECK_NUM_EQ(expected, m, num_headers); + + int r; + for (i = 0; i < m->num_headers; i++) { + r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]); + if (!r) return 0; + r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]); + if (!r) return 0; + } + + if (!connect) { + MESSAGE_CHECK_STR_EQ(expected, m, upgrade); + } + + return 1; +} + +/* Given a sequence of varargs messages, return the number of them that the + * parser should successfully parse, taking into account that upgraded + * messages prevent all subsequent messages from being parsed. + */ +size_t +count_parsed_messages(const size_t nmsgs, ...) { + size_t i; + va_list ap; + + va_start(ap, nmsgs); + + for (i = 0; i < nmsgs; i++) { + struct message *m = va_arg(ap, struct message *); + + if (m->upgrade) { + va_end(ap); + return i + 1; + } + } + + va_end(ap); + return nmsgs; +} + +/* Given a sequence of bytes and the number of these that we were able to + * parse, verify that upgrade bodies are correct. + */ +void +upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) { + va_list ap; + size_t i; + size_t off = 0; + + va_start(ap, nmsgs); + + for (i = 0; i < nmsgs; i++) { + struct message *m = va_arg(ap, struct message *); + + off += strlen(m->raw); + + if (m->upgrade) { + off -= strlen(m->upgrade); + + /* Check the portion of the response after its specified upgrade */ + if (!check_str_eq(m, "upgrade", body + off, body + nread)) { + abort(); + } + + /* Fix up the response so that message_eq() will verify the beginning + * of the upgrade */ + *(body + nread + strlen(m->upgrade)) = '\0'; + messages[num_messages -1 ].upgrade = body + nread; + + va_end(ap); + return; + } + } + + va_end(ap); + printf("\n\n*** Error: expected a message with upgrade ***\n"); + + abort(); +} + +static void +print_error (const char *raw, size_t error_location) +{ + fprintf(stderr, "\n*** %s ***\n\n", + http_errno_description(HTTP_PARSER_ERRNO(parser))); + + int this_line = 0, char_len = 0; + size_t i, j, len = strlen(raw), error_location_line = 0; + for (i = 0; i < len; i++) { + if (i == error_location) this_line = 1; + switch (raw[i]) { + case '\r': + char_len = 2; + fprintf(stderr, "\\r"); + break; + + case '\n': + fprintf(stderr, "\\n\n"); + + if (this_line) goto print; + + error_location_line = 0; + continue; + + default: + char_len = 1; + fputc(raw[i], stderr); + break; + } + if (!this_line) error_location_line += char_len; + } + + fprintf(stderr, "[eof]\n"); + + print: + for (j = 0; j < error_location_line; j++) { + fputc(' ', stderr); + } + fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location); +} + +void +test_preserve_data (void) +{ + char my_data[] = "application-specific data"; + http_parser parser; + parser.data = my_data; + http_parser_init(&parser, HTTP_REQUEST); + if (parser.data != my_data) { + printf("\n*** parser.data not preserved accross http_parser_init ***\n\n"); + abort(); + } +} + +struct url_test { + const char *name; + const char *url; + int is_connect; + struct http_parser_url u; + int rv; +}; + +const struct url_test url_tests[] = +{ {.name="proxy request" + ,.url="http://hostname/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 7, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 15, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="proxy request with port" + ,.url="http://hostname:444/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) + ,.port=444 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 7, 8 } /* UF_HOST */ + ,{ 16, 3 } /* UF_PORT */ + ,{ 19, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="CONNECT request" + ,.url="hostname:443" + ,.is_connect=1 + ,.u= + {.field_set=(1 << UF_HOST) | (1 << UF_PORT) + ,.port=443 + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 0, 8 } /* UF_HOST */ + ,{ 9, 3 } /* UF_PORT */ + ,{ 0, 0 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="CONNECT request but not connect" + ,.url="hostname:443" + ,.is_connect=0 + ,.rv=1 + } + +, {.name="proxy ipv6 request" + ,.url="http://[1:2::3:4]/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 17, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="proxy ipv6 request with port" + ,.url="http://[1:2::3:4]:67/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH) + ,.port=67 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 8 } /* UF_HOST */ + ,{ 18, 2 } /* UF_PORT */ + ,{ 20, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="CONNECT ipv6 address" + ,.url="[1:2::3:4]:443" + ,.is_connect=1 + ,.u= + {.field_set=(1 << UF_HOST) | (1 << UF_PORT) + ,.port=443 + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 1, 8 } /* UF_HOST */ + ,{ 11, 3 } /* UF_PORT */ + ,{ 0, 0 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="ipv4 in ipv6 address" + ,.url="http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/" + ,.is_connect=0 + ,.u= + {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 37 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 46, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="extra ? in query string" + ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css," + "fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css," + "fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css" + ,.is_connect=0 + ,.u= + {.field_set=(1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 7, 10 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 17, 12 } /* UF_PATH */ + ,{ 30,187 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="space URL encoded" + ,.url="/toto.html?toto=a%20b" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_PATH) | (1<<UF_QUERY) + ,.port=0 + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 0, 0 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 0, 10 } /* UF_PATH */ + ,{ 11, 10 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + + +, {.name="URL fragment" + ,.url="/toto.html#titi" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_PATH) | (1<<UF_FRAGMENT) + ,.port=0 + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 0, 0 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 0, 10 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 11, 4 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="complex URL fragment" + ,.url="http://www.webmasterworld.com/r.cgi?f=21&d=8405&url=" + "http://www.example.com/index.html?foo=bar&hello=world#midpage" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY) |\ + (1<<UF_FRAGMENT) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 7, 22 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 29, 6 } /* UF_PATH */ + ,{ 36, 69 } /* UF_QUERY */ + ,{106, 7 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="complex URL from node js url parser doc" + ,.url="http://host.com:8080/p/a/t/h?query=string#hash" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\ + (1<<UF_QUERY) | (1<<UF_FRAGMENT) + ,.port=8080 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 7, 8 } /* UF_HOST */ + ,{ 16, 4 } /* UF_PORT */ + ,{ 20, 8 } /* UF_PATH */ + ,{ 29, 12 } /* UF_QUERY */ + ,{ 42, 4 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="complex URL with basic auth from node js url parser doc" + ,.url="http://a:b@host.com:8080/p/a/t/h?query=string#hash" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\ + (1<<UF_QUERY) | (1<<UF_FRAGMENT) | (1<<UF_USERINFO) + ,.port=8080 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 11, 8 } /* UF_HOST */ + ,{ 20, 4 } /* UF_PORT */ + ,{ 24, 8 } /* UF_PATH */ + ,{ 33, 12 } /* UF_QUERY */ + ,{ 46, 4 } /* UF_FRAGMENT */ + ,{ 7, 3 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="double @" + ,.url="http://a:b@@hostname:443/" + ,.is_connect=0 + ,.rv=1 + } + +, {.name="proxy empty host" + ,.url="http://:443/" + ,.is_connect=0 + ,.rv=1 + } + +, {.name="proxy empty port" + ,.url="http://hostname:/" + ,.is_connect=0 + ,.rv=1 + } + +, {.name="CONNECT with basic auth" + ,.url="a:b@hostname:443" + ,.is_connect=1 + ,.rv=1 + } + +, {.name="CONNECT empty host" + ,.url=":443" + ,.is_connect=1 + ,.rv=1 + } + +, {.name="CONNECT empty port" + ,.url="hostname:" + ,.is_connect=1 + ,.rv=1 + } + +, {.name="CONNECT with extra bits" + ,.url="hostname:443/" + ,.is_connect=1 + ,.rv=1 + } + +, {.name="space in URL" + ,.url="/foo bar/" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy basic auth with space url encoded" + ,.url="http://a%20:b@host.com/" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 14, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 22, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 7, 6 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="carriage return in URL" + ,.url="/foo\rbar/" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy double : in URL" + ,.url="http://hostname::443/" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy basic auth with double :" + ,.url="http://a::b@host.com/" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 12, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 20, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 7, 4 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="line feed in URL" + ,.url="/foo\nbar/" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy empty basic auth" + ,.url="http://@hostname/fo" + ,.u= + {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 16, 3 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } +, {.name="proxy line feed in hostname" + ,.url="http://host\name/fo" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy % in hostname" + ,.url="http://host%name/fo" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy ; in hostname" + ,.url="http://host;ame/fo" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy basic auth with unreservedchars" + ,.url="http://a!;-_!=+$@host.com/" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 17, 8 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 25, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 7, 9 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="proxy only empty basic auth" + ,.url="http://@/fo" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy only basic auth" + ,.url="http://toto@/fo" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy emtpy hostname" + ,.url="http:///fo" + ,.rv=1 /* s_dead */ + } + +, {.name="proxy = in URL" + ,.url="http://host=ame/fo" + ,.rv=1 /* s_dead */ + } + +, {.name="ipv6 address with Zone ID" + ,.url="http://[fe80::a%25eth0]/" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 14 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 23, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="ipv6 address with Zone ID, but '%' is not percent-encoded" + ,.url="http://[fe80::a%eth0]/" + ,.is_connect=0 + ,.u= + {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) + ,.port=0 + ,.field_data= + {{ 0, 4 } /* UF_SCHEMA */ + ,{ 8, 12 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 21, 1 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="ipv6 address ending with '%'" + ,.url="http://[fe80::a%]/" + ,.rv=1 /* s_dead */ + } + +, {.name="ipv6 address with Zone ID including bad character" + ,.url="http://[fe80::a%$HOME]/" + ,.rv=1 /* s_dead */ + } + +, {.name="just ipv6 Zone ID" + ,.url="http://[%eth0]/" + ,.rv=1 /* s_dead */ + } + +#if HTTP_PARSER_STRICT + +, {.name="tab in URL" + ,.url="/foo\tbar/" + ,.rv=1 /* s_dead */ + } + +, {.name="form feed in URL" + ,.url="/foo\fbar/" + ,.rv=1 /* s_dead */ + } + +#else /* !HTTP_PARSER_STRICT */ + +, {.name="tab in URL" + ,.url="/foo\tbar/" + ,.u= + {.field_set=(1 << UF_PATH) + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 0, 0 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 0, 9 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } + +, {.name="form feed in URL" + ,.url="/foo\fbar/" + ,.u= + {.field_set=(1 << UF_PATH) + ,.field_data= + {{ 0, 0 } /* UF_SCHEMA */ + ,{ 0, 0 } /* UF_HOST */ + ,{ 0, 0 } /* UF_PORT */ + ,{ 0, 9 } /* UF_PATH */ + ,{ 0, 0 } /* UF_QUERY */ + ,{ 0, 0 } /* UF_FRAGMENT */ + ,{ 0, 0 } /* UF_USERINFO */ + } + } + ,.rv=0 + } +#endif +}; + +void +dump_url (const char *url, const struct http_parser_url *u) +{ + unsigned int i; + + printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port); + for (i = 0; i < UF_MAX; i++) { + if ((u->field_set & (1 << i)) == 0) { + printf("\tfield_data[%u]: unset\n", i); + continue; + } + + printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"", + i, + u->field_data[i].off, + u->field_data[i].len, + u->field_data[i].len, + url + u->field_data[i].off); + } +} + +void +test_parse_url (void) +{ + struct http_parser_url u; + const struct url_test *test; + unsigned int i; + int rv; + + for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) { + test = &url_tests[i]; + memset(&u, 0, sizeof(u)); + + rv = http_parser_parse_url(test->url, + strlen(test->url), + test->is_connect, + &u); + + if (test->rv == 0) { + if (rv != 0) { + printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " + "unexpected rv %d ***\n\n", test->url, test->name, rv); + abort(); + } + + if (memcmp(&u, &test->u, sizeof(u)) != 0) { + printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n", + test->url, test->name); + + printf("target http_parser_url:\n"); + dump_url(test->url, &test->u); + printf("result http_parser_url:\n"); + dump_url(test->url, &u); + + abort(); + } + } else { + /* test->rv != 0 */ + if (rv == 0) { + printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, " + "unexpected rv %d ***\n\n", test->url, test->name, rv); + abort(); + } + } + } +} + +void +test_method_str (void) +{ + assert(0 == strcmp("GET", http_method_str(HTTP_GET))); + assert(0 == strcmp("<unknown>", http_method_str(1337))); +} + +void +test_message (const struct message *message) +{ + size_t raw_len = strlen(message->raw); + size_t msg1len; + for (msg1len = 0; msg1len < raw_len; msg1len++) { + parser_init(message->type); + + size_t read; + const char *msg1 = message->raw; + const char *msg2 = msg1 + msg1len; + size_t msg2len = raw_len - msg1len; + + if (msg1len) { + read = parse(msg1, msg1len); + + if (message->upgrade && parser->upgrade && num_messages > 0) { + messages[num_messages - 1].upgrade = msg1 + read; + goto test; + } + + if (read != msg1len) { + print_error(msg1, read); + abort(); + } + } + + + read = parse(msg2, msg2len); + + if (message->upgrade && parser->upgrade) { + messages[num_messages - 1].upgrade = msg2 + read; + goto test; + } + + if (read != msg2len) { + print_error(msg2, read); + abort(); + } + + read = parse(NULL, 0); + + if (read != 0) { + print_error(message->raw, read); + abort(); + } + + test: + + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); + abort(); + } + + if(!message_eq(0, 0, message)) abort(); + + parser_free(); + } +} + +void +test_message_count_body (const struct message *message) +{ + parser_init(message->type); + + size_t read; + size_t l = strlen(message->raw); + size_t i, toread; + size_t chunk = 4024; + + for (i = 0; i < l; i+= chunk) { + toread = MIN(l-i, chunk); + read = parse_count_body(message->raw + i, toread); + if (read != toread) { + print_error(message->raw, read); + abort(); + } + } + + + read = parse_count_body(NULL, 0); + if (read != 0) { + print_error(message->raw, read); + abort(); + } + + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); + abort(); + } + + if(!message_eq(0, 0, message)) abort(); + + parser_free(); +} + +void +test_simple_type (const char *buf, + enum http_errno err_expected, + enum http_parser_type type) +{ + parser_init(type); + + enum http_errno err; + + parse(buf, strlen(buf)); + err = HTTP_PARSER_ERRNO(parser); + parse(NULL, 0); + + parser_free(); + + /* In strict mode, allow us to pass with an unexpected HPE_STRICT as + * long as the caller isn't expecting success. + */ +#if HTTP_PARSER_STRICT + if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) { +#else + if (err_expected != err) { +#endif + fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n", + http_errno_name(err_expected), http_errno_name(err), buf); + abort(); + } +} + +void +test_simple (const char *buf, enum http_errno err_expected) +{ + test_simple_type(buf, err_expected, HTTP_REQUEST); +} + +void +test_invalid_header_content (int req, const char* str) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + const char *buf; + buf = req ? + "GET / HTTP/1.1\r\n" : + "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + assert(parsed == strlen(buf)); + + buf = str; + size_t buflen = strlen(buf); + + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN); + return; + } + + fprintf(stderr, + "\n*** Error expected but none in invalid header content test ***\n"); + abort(); +} + +void +test_invalid_header_field_content_error (int req) +{ + test_invalid_header_content(req, "Foo: F\01ailure"); + test_invalid_header_content(req, "Foo: B\02ar"); +} + +void +test_invalid_header_field (int req, const char* str) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + const char *buf; + buf = req ? + "GET / HTTP/1.1\r\n" : + "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + assert(parsed == strlen(buf)); + + buf = str; + size_t buflen = strlen(buf); + + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN); + return; + } + + fprintf(stderr, + "\n*** Error expected but none in invalid header token test ***\n"); + abort(); +} + +void +test_invalid_header_field_token_error (int req) +{ + test_invalid_header_field(req, "Fo@: Failure"); + test_invalid_header_field(req, "Foo\01\test: Bar"); +} + +void +test_double_content_length_error (int req) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + const char *buf; + buf = req ? + "GET / HTTP/1.1\r\n" : + "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + assert(parsed == strlen(buf)); + + buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n"; + size_t buflen = strlen(buf); + + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH); + return; + } + + fprintf(stderr, + "\n*** Error expected but none in double content-length test ***\n"); + abort(); +} + +void +test_chunked_content_length_error (int req) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + const char *buf; + buf = req ? + "GET / HTTP/1.1\r\n" : + "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + assert(parsed == strlen(buf)); + + buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n"; + size_t buflen = strlen(buf); + + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH); + return; + } + + fprintf(stderr, + "\n*** Error expected but none in chunked content-length test ***\n"); + abort(); +} + +void +test_header_cr_no_lf_error (int req) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + const char *buf; + buf = req ? + "GET / HTTP/1.1\r\n" : + "HTTP/1.1 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + assert(parsed == strlen(buf)); + + buf = "Foo: 1\rBar: 1\r\n\r\n"; + size_t buflen = strlen(buf); + + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + assert(HTTP_PARSER_ERRNO(&parser) == HPE_LF_EXPECTED); + return; + } + + fprintf(stderr, + "\n*** Error expected but none in header whitespace test ***\n"); + abort(); +} + +void +test_header_overflow_error (int req) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + const char *buf; + buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + assert(parsed == strlen(buf)); + + buf = "header-key: header-value\r\n"; + size_t buflen = strlen(buf); + + int i; + for (i = 0; i < 10000; i++) { + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { + //fprintf(stderr, "error found on iter %d\n", i); + assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW); + return; + } + } + + fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n"); + abort(); +} + + +void +test_header_nread_value () +{ + http_parser parser; + http_parser_init(&parser, HTTP_REQUEST); + size_t parsed; + const char *buf; + buf = "GET / HTTP/1.1\r\nheader: value\nhdr: value\r\n"; + parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf)); + assert(parsed == strlen(buf)); + + assert(parser.nread == strlen(buf)); +} + + +static void +test_content_length_overflow (const char *buf, size_t buflen, int expect_ok) +{ + http_parser parser; + http_parser_init(&parser, HTTP_RESPONSE); + http_parser_execute(&parser, &settings_null, buf, buflen); + + if (expect_ok) + assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK); + else + assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH); +} + +void +test_header_content_length_overflow_error (void) +{ +#define X(size) \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Length: " #size "\r\n" \ + "\r\n" + const char a[] = X(1844674407370955160); /* 2^64 / 10 - 1 */ + const char b[] = X(18446744073709551615); /* 2^64-1 */ + const char c[] = X(18446744073709551616); /* 2^64 */ +#undef X + test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ + test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ + test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ +} + +void +test_chunk_content_length_overflow_error (void) +{ +#define X(size) \ + "HTTP/1.1 200 OK\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "\r\n" \ + #size "\r\n" \ + "..." + const char a[] = X(FFFFFFFFFFFFFFE); /* 2^64 / 16 - 1 */ + const char b[] = X(FFFFFFFFFFFFFFFF); /* 2^64-1 */ + const char c[] = X(10000000000000000); /* 2^64 */ +#undef X + test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok */ + test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */ + test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */ +} + +void +test_no_overflow_long_body (int req, size_t length) +{ + http_parser parser; + http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE); + size_t parsed; + size_t i; + char buf1[3000]; + size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n", + req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length); + parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); + if (parsed != buf1len) + goto err; + + for (i = 0; i < length; i++) { + char foo = 'a'; + parsed = http_parser_execute(&parser, &settings_null, &foo, 1); + if (parsed != 1) + goto err; + } + + parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len); + if (parsed != buf1len) goto err; + return; + + err: + fprintf(stderr, + "\n*** error in test_no_overflow_long_body %s of length %lu ***\n", + req ? "REQUEST" : "RESPONSE", + (unsigned long)length); + abort(); +} + +void +test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3) +{ + int message_count = count_parsed_messages(3, r1, r2, r3); + + char total[ strlen(r1->raw) + + strlen(r2->raw) + + strlen(r3->raw) + + 1 + ]; + total[0] = '\0'; + + strcat(total, r1->raw); + strcat(total, r2->raw); + strcat(total, r3->raw); + + parser_init(r1->type); + + size_t read; + + read = parse(total, strlen(total)); + + if (parser->upgrade) { + upgrade_message_fix(total, read, 3, r1, r2, r3); + goto test; + } + + if (read != strlen(total)) { + print_error(total, read); + abort(); + } + + read = parse(NULL, 0); + + if (read != 0) { + print_error(total, read); + abort(); + } + +test: + + if (message_count != num_messages) { + fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages); + abort(); + } + + if (!message_eq(0, 0, r1)) abort(); + if (message_count > 1 && !message_eq(1, 0, r2)) abort(); + if (message_count > 2 && !message_eq(2, 0, r3)) abort(); + + parser_free(); +} + +/* SCAN through every possible breaking to make sure the + * parser can handle getting the content in any chunks that + * might come from the socket + */ +void +test_scan (const struct message *r1, const struct message *r2, const struct message *r3) +{ + char total[80*1024] = "\0"; + char buf1[80*1024] = "\0"; + char buf2[80*1024] = "\0"; + char buf3[80*1024] = "\0"; + + strcat(total, r1->raw); + strcat(total, r2->raw); + strcat(total, r3->raw); + + size_t read; + + int total_len = strlen(total); + + int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2; + int ops = 0 ; + + size_t buf1_len, buf2_len, buf3_len; + int message_count = count_parsed_messages(3, r1, r2, r3); + + int i,j,type_both; + for (type_both = 0; type_both < 2; type_both ++ ) { + for (j = 2; j < total_len; j ++ ) { + for (i = 1; i < j; i ++ ) { + + if (ops % 1000 == 0) { + printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops); + fflush(stdout); + } + ops += 1; + + parser_init(type_both ? HTTP_BOTH : r1->type); + + buf1_len = i; + strlncpy(buf1, sizeof(buf1), total, buf1_len); + buf1[buf1_len] = 0; + + buf2_len = j - i; + strlncpy(buf2, sizeof(buf1), total+i, buf2_len); + buf2[buf2_len] = 0; + + buf3_len = total_len - j; + strlncpy(buf3, sizeof(buf1), total+j, buf3_len); + buf3[buf3_len] = 0; + + read = parse(buf1, buf1_len); + + if (parser->upgrade) goto test; + + if (read != buf1_len) { + print_error(buf1, read); + goto error; + } + + read += parse(buf2, buf2_len); + + if (parser->upgrade) goto test; + + if (read != buf1_len + buf2_len) { + print_error(buf2, read); + goto error; + } + + read += parse(buf3, buf3_len); + + if (parser->upgrade) goto test; + + if (read != buf1_len + buf2_len + buf3_len) { + print_error(buf3, read); + goto error; + } + + parse(NULL, 0); + +test: + if (parser->upgrade) { + upgrade_message_fix(total, read, 3, r1, r2, r3); + } + + if (message_count != num_messages) { + fprintf(stderr, "\n\nParser didn't see %d messages only %d\n", + message_count, num_messages); + goto error; + } + + if (!message_eq(0, 0, r1)) { + fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n"); + goto error; + } + + if (message_count > 1 && !message_eq(1, 0, r2)) { + fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n"); + goto error; + } + + if (message_count > 2 && !message_eq(2, 0, r3)) { + fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n"); + goto error; + } + + parser_free(); + } + } + } + puts("\b\b\b\b100%"); + return; + + error: + fprintf(stderr, "i=%d j=%d\n", i, j); + fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1); + fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2); + fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3); + abort(); +} + +// user required to free the result +// string terminated by \0 +char * +create_large_chunked_message (int body_size_in_kb, const char* headers) +{ + int i; + size_t wrote = 0; + size_t headers_len = strlen(headers); + size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6; + char * buf = malloc(bufsize); + + memcpy(buf, headers, headers_len); + wrote += headers_len; + + for (i = 0; i < body_size_in_kb; i++) { + // write 1kb chunk into the body. + memcpy(buf + wrote, "400\r\n", 5); + wrote += 5; + memset(buf + wrote, 'C', 1024); + wrote += 1024; + strcpy(buf + wrote, "\r\n"); + wrote += 2; + } + + memcpy(buf + wrote, "0\r\n\r\n", 6); + wrote += 6; + assert(wrote == bufsize); + + return buf; +} + +/* Verify that we can pause parsing at any of the bytes in the + * message and still get the result that we're expecting. */ +void +test_message_pause (const struct message *msg) +{ + char *buf = (char*) msg->raw; + size_t buflen = strlen(msg->raw); + size_t nread; + + parser_init(msg->type); + + do { + nread = parse_pause(buf, buflen); + + // We can only set the upgrade buffer once we've gotten our message + // completion callback. + if (messages[0].message_complete_cb_called && + msg->upgrade && + parser->upgrade) { + messages[0].upgrade = buf + nread; + goto test; + } + + if (nread < buflen) { + + // Not much do to if we failed a strict-mode check + if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) { + parser_free(); + return; + } + + assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED); + } + + buf += nread; + buflen -= nread; + http_parser_pause(parser, 0); + } while (buflen > 0); + + nread = parse_pause(NULL, 0); + assert (nread == 0); + +test: + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); + abort(); + } + + if(!message_eq(0, 0, msg)) abort(); + + parser_free(); +} + +/* Verify that body and next message won't be parsed in responses to CONNECT */ +void +test_message_connect (const struct message *msg) +{ + char *buf = (char*) msg->raw; + size_t buflen = strlen(msg->raw); + + parser_init(msg->type); + + parse_connect(buf, buflen); + + if (num_messages != 1) { + printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name); + abort(); + } + + if(!message_eq(0, 1, msg)) abort(); + + parser_free(); +} + +int +main (void) +{ + parser = NULL; + int i, j, k; + int request_count; + int response_count; + unsigned long version; + unsigned major; + unsigned minor; + unsigned patch; + + version = http_parser_version(); + major = (version >> 16) & 255; + minor = (version >> 8) & 255; + patch = version & 255; + printf("http_parser v%u.%u.%u (0x%06lx)\n", major, minor, patch, version); + + printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser)); + + for (request_count = 0; requests[request_count].name; request_count++); + for (response_count = 0; responses[response_count].name; response_count++); + + //// API + test_preserve_data(); + test_parse_url(); + test_method_str(); + + //// NREAD + test_header_nread_value(); + + //// OVERFLOW CONDITIONS + + test_header_overflow_error(HTTP_REQUEST); + test_no_overflow_long_body(HTTP_REQUEST, 1000); + test_no_overflow_long_body(HTTP_REQUEST, 100000); + + test_header_overflow_error(HTTP_RESPONSE); + test_no_overflow_long_body(HTTP_RESPONSE, 1000); + test_no_overflow_long_body(HTTP_RESPONSE, 100000); + + test_header_content_length_overflow_error(); + test_chunk_content_length_overflow_error(); + + //// HEADER FIELD CONDITIONS + test_double_content_length_error(HTTP_REQUEST); + test_chunked_content_length_error(HTTP_REQUEST); + test_header_cr_no_lf_error(HTTP_REQUEST); + test_invalid_header_field_token_error(HTTP_REQUEST); + test_invalid_header_field_content_error(HTTP_REQUEST); + test_double_content_length_error(HTTP_RESPONSE); + test_chunked_content_length_error(HTTP_RESPONSE); + test_header_cr_no_lf_error(HTTP_RESPONSE); + test_invalid_header_field_token_error(HTTP_RESPONSE); + test_invalid_header_field_content_error(HTTP_RESPONSE); + + //// RESPONSES + + test_simple_type("HTP/1.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); + test_simple_type("HTTP/01.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); + test_simple_type("HTTP/11.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); + test_simple_type("HTTP/1.01 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); + test_simple_type("HTTP/1.1\t200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE); + + for (i = 0; i < response_count; i++) { + test_message(&responses[i]); + } + + for (i = 0; i < response_count; i++) { + test_message_pause(&responses[i]); + } + + for (i = 0; i < response_count; i++) { + test_message_connect(&responses[i]); + } + + for (i = 0; i < response_count; i++) { + if (!responses[i].should_keep_alive) continue; + for (j = 0; j < response_count; j++) { + if (!responses[j].should_keep_alive) continue; + for (k = 0; k < response_count; k++) { + test_multiple3(&responses[i], &responses[j], &responses[k]); + } + } + } + + test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]); + test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]); + + // test very large chunked response + { + char * msg = create_large_chunked_message(31337, + "HTTP/1.0 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Type: text/plain\r\n" + "\r\n"); + struct message large_chunked = + {.name= "large chunked" + ,.type= HTTP_RESPONSE + ,.raw= msg + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.status_code= 200 + ,.response_status= "OK" + ,.num_headers= 2 + ,.headers= + { { "Transfer-Encoding", "chunked" } + , { "Content-Type", "text/plain" } + } + ,.body_size= 31337*1024 + ,.num_chunks_complete= 31338 + }; + for (i = 0; i < MAX_CHUNKS; i++) { + large_chunked.chunk_lengths[i] = 1024; + } + test_message_count_body(&large_chunked); + free(msg); + } + + + + printf("response scan 1/2 "); + test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY] + , &responses[NO_BODY_HTTP10_KA_204] + , &responses[NO_REASON_PHRASE] + ); + + printf("response scan 2/2 "); + test_scan( &responses[BONJOUR_MADAME_FR] + , &responses[UNDERSTORE_HEADER_KEY] + , &responses[NO_CARRIAGE_RET] + ); + + puts("responses okay"); + + + /// REQUESTS + + test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION); + test_simple("GET / HTTP/01.1\r\n\r\n", HPE_INVALID_VERSION); + test_simple("GET / HTTP/11.1\r\n\r\n", HPE_INVALID_VERSION); + test_simple("GET / HTTP/1.01\r\n\r\n", HPE_INVALID_VERSION); + + // Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js + test_simple("GET / HTTP/1.1\r\n" + "Test: Düsseldorf\r\n", + HPE_OK); + + // Well-formed but incomplete + test_simple("GET / HTTP/1.1\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 6\r\n" + "\r\n" + "fooba", + HPE_OK); + + static const char *all_methods[] = { + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + "PURGE", + "MKCALENDAR", + "LINK", + "UNLINK", + 0 }; + const char **this_method; + for (this_method = all_methods; *this_method; this_method++) { + char buf[200]; + sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); + test_simple(buf, HPE_OK); + } + + static const char *bad_methods[] = { + "ASDF", + "C******", + "COLA", + "GEM", + "GETA", + "M****", + "MKCOLA", + "PROPPATCHA", + "PUN", + "PX", + "SA", + "hello world", + 0 }; + for (this_method = bad_methods; *this_method; this_method++) { + char buf[200]; + sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); + test_simple(buf, HPE_INVALID_METHOD); + } + + // illegal header field name line folding + test_simple("GET / HTTP/1.1\r\n" + "name\r\n" + " : value\r\n" + "\r\n", + HPE_INVALID_HEADER_TOKEN); + + const char *dumbfuck2 = + "GET / HTTP/1.1\r\n" + "X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n" + "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" + "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" + "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" + "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" + "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" + "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" + "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" + "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" + "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" + "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" + "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" + "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" + "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" + "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" + "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" + "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" + "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" + "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" + "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" + "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" + "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" + "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" + "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" + "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" + "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" + "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" + "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" + "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" + "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" + "\tRA==\r\n" + "\t-----END CERTIFICATE-----\r\n" + "\r\n"; + test_simple(dumbfuck2, HPE_OK); + + const char *corrupted_connection = + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "Connection\r\033\065\325eep-Alive\r\n" + "Accept-Encoding: gzip\r\n" + "\r\n"; + test_simple(corrupted_connection, HPE_INVALID_HEADER_TOKEN); + + const char *corrupted_header_name = + "GET / HTTP/1.1\r\n" + "Host: www.example.com\r\n" + "X-Some-Header\r\033\065\325eep-Alive\r\n" + "Accept-Encoding: gzip\r\n" + "\r\n"; + test_simple(corrupted_header_name, HPE_INVALID_HEADER_TOKEN); + +#if 0 + // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body + // until EOF. + // + // no content-length + // error if there is a body without content length + const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n" + "Accept: */*\r\n" + "\r\n" + "HELLO"; + test_simple(bad_get_no_headers_no_body, 0); +#endif + /* TODO sending junk and large headers gets rejected */ + + + /* check to make sure our predefined requests are okay */ + for (i = 0; requests[i].name; i++) { + test_message(&requests[i]); + } + + for (i = 0; i < request_count; i++) { + test_message_pause(&requests[i]); + } + + for (i = 0; i < request_count; i++) { + if (!requests[i].should_keep_alive) continue; + for (j = 0; j < request_count; j++) { + if (!requests[j].should_keep_alive) continue; + for (k = 0; k < request_count; k++) { + test_multiple3(&requests[i], &requests[j], &requests[k]); + } + } + } + + printf("request scan 1/4 "); + test_scan( &requests[GET_NO_HEADERS_NO_BODY] + , &requests[GET_ONE_HEADER_NO_BODY] + , &requests[GET_NO_HEADERS_NO_BODY] + ); + + printf("request scan 2/4 "); + test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE] + , &requests[POST_IDENTITY_BODY_WORLD] + , &requests[GET_FUNKY_CONTENT_LENGTH] + ); + + printf("request scan 3/4 "); + test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END] + , &requests[CHUNKED_W_TRAILING_HEADERS] + , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH] + ); + + printf("request scan 4/4 "); + test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET] + , &requests[PREFIX_NEWLINE_GET ] + , &requests[CONNECT_REQUEST] + ); + + puts("requests okay"); + + return 0; +} |